From 59114bc7490745ebee5041cc0162b95282934903 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 10 Aug 2017 13:48:20 +0200 Subject: [PATCH] adapt code to work with some server side json property renamings, distinguish between tracingId and annotationId, fix unit tests (#1759) --- .../javascripts/oxalis/api/api_latest.js | 8 +- app/assets/javascripts/oxalis/controller.js | 4 +- .../oxalis/controller/url_manager.js | 12 +- app/assets/javascripts/oxalis/model.js | 22 ++- .../oxalis/model/actions/save_actions.js | 1 + .../model/reducers/readonlytracing_reducer.js | 3 +- .../model/reducers/skeletontracing_reducer.js | 10 +- .../model/reducers/volumetracing_reducer.js | 3 +- .../oxalis/model/sagas/annotation_saga.js | 2 +- .../oxalis/model/sagas/save_saga.js | 26 ++- app/assets/javascripts/oxalis/store.js | 7 +- .../view/action-bar/dataset_actions_view.js | 4 +- .../view/action-bar/merge_modal_view.js | 12 +- .../oxalis/view/tracing_layout_view.js | 4 +- app/assets/javascripts/router.js | 4 +- .../test/api/api_skeleton_latest.spec.js | 4 +- .../javascripts/test/api/api_v1.spec.js | 99 +--------- .../test/api/api_volume_latest.spec.js | 4 +- .../test/controller/url_manager.spec.js | 18 +- .../test/fixtures/dataset_server_object.js | 40 ++++ .../test/fixtures/skeletontracing_object.js | 182 ------------------ .../skeletontracing_server_objects.js | 87 +++++++++ .../test/fixtures/volumetracing_object.js | 121 ------------ .../fixtures/volumetracing_server_objects.js | 55 ++++++ .../javascripts/test/helpers/apiHelpers.js | 50 ++++- .../javascripts/test/model/model.spec.js | 70 +++---- .../javascripts/test/sagas/save_saga.spec.js | 31 +-- 27 files changed, 368 insertions(+), 515 deletions(-) create mode 100644 app/assets/javascripts/test/fixtures/dataset_server_object.js delete mode 100644 app/assets/javascripts/test/fixtures/skeletontracing_object.js create mode 100644 app/assets/javascripts/test/fixtures/skeletontracing_server_objects.js delete mode 100644 app/assets/javascripts/test/fixtures/volumetracing_object.js create mode 100644 app/assets/javascripts/test/fixtures/volumetracing_server_objects.js diff --git a/app/assets/javascripts/oxalis/api/api_latest.js b/app/assets/javascripts/oxalis/api/api_latest.js index fe57cb07229..7d02e8624b0 100644 --- a/app/assets/javascripts/oxalis/api/api_latest.js +++ b/app/assets/javascripts/oxalis/api/api_latest.js @@ -234,9 +234,9 @@ class TracingApi { */ async finishAndGetNextTask() { const state = Store.getState(); - const { tracingType, tracingId } = state.tracing; + const { tracingType, annotationId } = state.tracing; const task = state.task; - const finishUrl = `/annotations/${tracingType}/${tracingId}/finish`; + const finishUrl = `/annotations/${tracingType}/${annotationId}/finish`; const requestTaskUrl = "/user/tasks/request"; await Model.save(); @@ -277,12 +277,12 @@ class TracingApi { */ async restart( newTracingType: SkeletonTracingTypeTracingType, - newTracingId: string, + newAnnotationId: string, newControlMode: ControlModeType, ) { Store.dispatch(restartSagaAction()); UrlManager.reset(); - await Model.fetch(newTracingType, newTracingId, newControlMode, false); + await Model.fetch(newTracingType, newAnnotationId, newControlMode, false); Store.dispatch(wkReadyAction()); UrlManager.updateUnthrottled(true); } diff --git a/app/assets/javascripts/oxalis/controller.js b/app/assets/javascripts/oxalis/controller.js index 48e32932ac1..03aedf530d1 100644 --- a/app/assets/javascripts/oxalis/controller.js +++ b/app/assets/javascripts/oxalis/controller.js @@ -39,7 +39,7 @@ import type { OxalisState, SkeletonTracingTypeTracingType } from "oxalis/store"; class Controller extends React.PureComponent { props: { initialTracingType: SkeletonTracingTypeTracingType, - initialTracingId: string, + initialAnnotationId: string, initialControlmode: ControlModeType, // Delivered by connect() viewMode: ModeType, @@ -84,7 +84,7 @@ class Controller extends React.PureComponent { Model.fetch( this.props.initialTracingType, - this.props.initialTracingId, + this.props.initialAnnotationId, this.props.initialControlmode, true, ) diff --git a/app/assets/javascripts/oxalis/controller/url_manager.js b/app/assets/javascripts/oxalis/controller/url_manager.js index 204b5fce425..19701056d1b 100644 --- a/app/assets/javascripts/oxalis/controller/url_manager.js +++ b/app/assets/javascripts/oxalis/controller/url_manager.js @@ -126,12 +126,16 @@ class UrlManager { } getActiveNode(tracing).map(node => state.push(node.id)); - const newBaseUrl = updateTypeAndId(this.baseUrl, tracing.tracingType, tracing.tracingId); + const newBaseUrl = updateTypeAndId(this.baseUrl, tracing.tracingType, tracing.annotationId); return `${newBaseUrl}#${state.join(",")}`; } } -export function updateTypeAndId(baseUrl: string, tracingType: string, tracingId: string): string { +export function updateTypeAndId( + baseUrl: string, + tracingType: string, + annotationId: string, +): string { // Update the baseUrl with a potentially new tracing id and or tracing type. // There are two possible routes (annotations or datasets) which will be handled // both here. Chaining the replace function is possible, since they are mutually @@ -139,11 +143,11 @@ export function updateTypeAndId(baseUrl: string, tracingType: string, tracingId: return baseUrl .replace( /^(.*\/annotations)\/(.*?)\/([^/]*)(\/?.*)$/, - (all, base, type, id, rest) => `${base}/${tracingType}/${tracingId}${rest}`, + (all, base, type, id, rest) => `${base}/${tracingType}/${annotationId}${rest}`, ) .replace( /^(.*\/datasets)\/([^/]*)(\/.*)$/, - (all, base, id, rest) => `${base}/${tracingId}${rest}`, + (all, base, id, rest) => `${base}/${annotationId}${rest}`, ); } diff --git a/app/assets/javascripts/oxalis/model.js b/app/assets/javascripts/oxalis/model.js index 1c97a677c62..4ba6b7345a6 100644 --- a/app/assets/javascripts/oxalis/model.js +++ b/app/assets/javascripts/oxalis/model.js @@ -54,7 +54,7 @@ import update from "immutability-helper"; import UrlManager from "oxalis/controller/url_manager"; type ServerSkeletonTracingTreeType = { - id: number, + treeId: number, color: ?Vector3, name: string, timestamp: number, @@ -65,11 +65,12 @@ type ServerSkeletonTracingTreeType = { }; export type ServerSkeletonTracingType = { - activeNode?: number, + activeNodeId?: number, boundingBox?: BoundingBoxObjectType, - customLayers: Array, + customLayers?: Array, editPosition: Vector3, editRotation: Vector3, + id: string, trees: Array, version: number, zoomLevel: number, @@ -78,9 +79,10 @@ export type ServerSkeletonTracingType = { export type ServerVolumeTracingType = { activeCell?: number, boundingBox?: BoundingBoxObjectType, - customLayers: Array, + customLayers?: Array, editPosition: Vector3, editRotation: Vector3, + id: string, nextCell: ?number, version: number, zoomLevel: number, @@ -133,7 +135,7 @@ export class OxalisModel { async fetch( tracingType: SkeletonTracingTypeTracingType, - tracingId: string, + annotationId: string, controlMode: ControlModeType, initialFetch: boolean, ) { @@ -144,9 +146,9 @@ export class OxalisModel { // Include /readOnly part whenever it is in the pathname const isReadOnly = window.location.pathname.endsWith("/readOnly"); const readOnlyPart = isReadOnly ? "readOnly/" : ""; - infoUrl = `/annotations/${tracingType}/${tracingId}/${readOnlyPart}info`; + infoUrl = `/annotations/${tracingType}/${annotationId}/${readOnlyPart}info`; } else { - infoUrl = `/annotations/${tracingType}/${tracingId}/info`; + infoUrl = `/annotations/${tracingType}/${annotationId}/info`; } const annotation: ServerAnnotationType = await Request.receiveJSON(infoUrl); @@ -160,7 +162,7 @@ export class OxalisModel { } else if (!dataset) { error = "Selected dataset doesn't exist"; } else if (!dataset.dataSource.dataLayers) { - const datasetName = dataset.dataSource.id.name; + const datasetName = annotation.dataSetName; if (datasetName) { error = `Please, double check if you have the dataset '${datasetName}' imported.`; } else { @@ -238,7 +240,7 @@ export class OxalisModel { const { allowedModes, preferredMode } = this.determineAllowedModes(annotation.settings); _.extend(annotation.settings, { allowedModes, preferredMode }); - const isVolume = annotation.settings.allowedModes.includes("volume"); + const isVolume = annotation.content.typ === "volume"; const controlMode = Store.getState().temporaryConfiguration.controlMode; if (controlMode === ControlModeEnum.TRACE) { if (isVolume) { @@ -259,7 +261,7 @@ export class OxalisModel { if (initialFetch) { // TODO remove default value - Store.dispatch(setZoomStepAction(tracing.zoomLevel || 2)); + Store.dispatch(setZoomStepAction(tracing.zoomLevel != null ? tracing.zoomLevel : 2)); } // Initialize 'flight', 'oblique' or 'orthogonal'/'volume' mode diff --git a/app/assets/javascripts/oxalis/model/actions/save_actions.js b/app/assets/javascripts/oxalis/model/actions/save_actions.js index 03ead442a08..5195c2de76a 100644 --- a/app/assets/javascripts/oxalis/model/actions/save_actions.js +++ b/app/assets/javascripts/oxalis/model/actions/save_actions.js @@ -4,6 +4,7 @@ * save_actions.js * @flow */ +import Date from "libs/date"; import type { UpdateAction } from "oxalis/model/sagas/update_actions"; type PushSaveQueueActionType = { diff --git a/app/assets/javascripts/oxalis/model/reducers/readonlytracing_reducer.js b/app/assets/javascripts/oxalis/model/reducers/readonlytracing_reducer.js index 919c8d1363e..cdf922d988a 100644 --- a/app/assets/javascripts/oxalis/model/reducers/readonlytracing_reducer.js +++ b/app/assets/javascripts/oxalis/model/reducers/readonlytracing_reducer.js @@ -15,11 +15,12 @@ function ReadOnlyTracingReducer(state: OxalisState, action: ActionType): OxalisS ); const readonlyTracing: ReadOnlyTracingType = { + annotationId: action.annotation.id, type: "readonly", restrictions, name: action.annotation.name, tracingType: "View", - tracingId: action.annotation.id, + tracingId: action.tracing.id, version: action.tracing.version, boundingBox: convertBoundingBox(action.tracing.boundingBox), }; diff --git a/app/assets/javascripts/oxalis/model/reducers/skeletontracing_reducer.js b/app/assets/javascripts/oxalis/model/reducers/skeletontracing_reducer.js index cccc2558f42..499aaf60c20 100644 --- a/app/assets/javascripts/oxalis/model/reducers/skeletontracing_reducer.js +++ b/app/assets/javascripts/oxalis/model/reducers/skeletontracing_reducer.js @@ -45,16 +45,15 @@ function SkeletonTracingReducer(state: OxalisState, action: ActionType): OxalisS const trees = _.keyBy( action.tracing.trees.map(tree => update(tree, { - treeId: { $set: tree.id }, nodes: { $set: _.keyBy(tree.nodes, "id") }, - color: { $set: tree.color || ColorGenerator.distinctColorForId(tree.id) }, + color: { $set: tree.color || ColorGenerator.distinctColorForId(tree.treeId) }, isVisible: { $set: true }, }), ), - "id", + "treeId", ); - const activeNodeIdMaybe = Maybe.fromNullable(action.tracing.activeNode); + const activeNodeIdMaybe = Maybe.fromNullable(action.tracing.activeNodeId); let cachedMaxNodeId = _.max(_.flatMap(trees, __ => _.map(__.nodes, node => node.id))); cachedMaxNodeId = cachedMaxNodeId != null ? cachedMaxNodeId : Constants.MIN_NODE_ID - 1; @@ -87,6 +86,7 @@ function SkeletonTracingReducer(state: OxalisState, action: ActionType): OxalisS const activeTreeId = Utils.toNullable(activeTreeIdMaybe); const skeletonTracing: SkeletonTracingType = { + annotationId: action.annotation.id, type: "skeleton", activeNodeId, cachedMaxNodeId, @@ -95,7 +95,7 @@ function SkeletonTracingReducer(state: OxalisState, action: ActionType): OxalisS trees, name: action.annotation.name, tracingType: action.annotation.typ, - tracingId: action.annotation.id, + tracingId: action.tracing.id, version: action.tracing.version, boundingBox: convertBoundingBox(action.tracing.boundingBox), }; diff --git a/app/assets/javascripts/oxalis/model/reducers/volumetracing_reducer.js b/app/assets/javascripts/oxalis/model/reducers/volumetracing_reducer.js index a12ddb8a153..290fdad9aa5 100644 --- a/app/assets/javascripts/oxalis/model/reducers/volumetracing_reducer.js +++ b/app/assets/javascripts/oxalis/model/reducers/volumetracing_reducer.js @@ -34,6 +34,7 @@ function VolumeTracingReducer(state: OxalisState, action: VolumeTracingActionTyp } const volumeTracing: VolumeTracingType = { + annotationId: action.annotation.id, type: "volume", activeCellId: 0, lastCentroid: null, @@ -44,7 +45,7 @@ function VolumeTracingReducer(state: OxalisState, action: VolumeTracingActionTyp volumeTraceOrMoveMode: Constants.VOLUME_MODE_MOVE, name: action.annotation.name, tracingType: action.annotation.typ, - tracingId: action.annotation.id, + tracingId: action.tracing.id, version: action.tracing.version, boundingBox: convertBoundingBox(action.tracing.boundingBox), }; diff --git a/app/assets/javascripts/oxalis/model/sagas/annotation_saga.js b/app/assets/javascripts/oxalis/model/sagas/annotation_saga.js index 4cbd0d3896c..bdef05f83dc 100644 --- a/app/assets/javascripts/oxalis/model/sagas/annotation_saga.js +++ b/app/assets/javascripts/oxalis/model/sagas/annotation_saga.js @@ -4,7 +4,7 @@ import Request from "libs/request"; export function* pushTracingNameAsync(): Generator<*, *, *> { const tracing = yield select(state => state.tracing); - const url = `/annotations/${tracing.tracingType}/${tracing.tracingId}/name`; + const url = `/annotations/${tracing.tracingType}/${tracing.annotationId}/name`; yield [ call(Request.sendJSONReceiveJSON, url, { data: { name: tracing.name }, diff --git a/app/assets/javascripts/oxalis/model/sagas/save_saga.js b/app/assets/javascripts/oxalis/model/sagas/save_saga.js index a71d67b8726..7822259f694 100644 --- a/app/assets/javascripts/oxalis/model/sagas/save_saga.js +++ b/app/assets/javascripts/oxalis/model/sagas/save_saga.js @@ -57,14 +57,19 @@ export function* pushAnnotationAsync(): Generator<*, *, *> { export function* sendRequestToServer(timestamp: number = Date.now()): Generator<*, *, *> { const batch = yield select(state => state.save.queue); let compactBatch = compactUpdateActions(batch); - const { version, tracingType, tracingId } = yield select(state => state.tracing); + const { version, type, tracingId } = yield select(state => state.tracing); + const dataStoreUrl = yield select(state => state.dataset.dataStore.url); compactBatch = addVersionNumbers(compactBatch, version); try { - yield call(Request.sendJSONReceiveJSON, `/annotations/${tracingType}/${tracingId}`, { - method: "PUT", - headers: { "X-Date": timestamp }, - data: compactBatch, - }); + yield call( + Request.sendJSONReceiveJSON, + `${dataStoreUrl}/data/tracings/${type}/${tracingId}/update`, + { + method: "POST", + headers: { "X-Date": timestamp }, + data: compactBatch, + }, + ); yield put(setVersionNumberAction(version + compactBatch.length)); yield put(setLastSaveTimestampAction()); yield put(shiftSaveQueueAction(batch.length)); @@ -227,13 +232,14 @@ function compactDeletedTrees(updateActions: Array) { const deletedTreeIds = updateActions .filter(ua => ua.name === "deleteTree") .map(ua => (ua.name === "deleteTree" ? ua.value.id : -1)); - _.remove( + return _.filter( updateActions, ua => - (ua.name === "deleteNode" || ua.name === "deleteEdge") && - deletedTreeIds.includes(ua.value.treeId), + !( + (ua.name === "deleteNode" || ua.name === "deleteEdge") && + deletedTreeIds.includes(ua.value.treeId) + ), ); - return updateActions; } export function compactUpdateActions( diff --git a/app/assets/javascripts/oxalis/store.js b/app/assets/javascripts/oxalis/store.js index 46e30a62310..e7c0fbdd69e 100644 --- a/app/assets/javascripts/oxalis/store.js +++ b/app/assets/javascripts/oxalis/store.js @@ -160,6 +160,7 @@ export type SkeletonTracingTypeTracingType = $Keys, -} +}; export type SaveStateType = { +isBusy: boolean, @@ -392,6 +395,8 @@ export const defaultState: OxalisState = { dataLayers: [], }, tracing: { + annotationId: "", + boundingBox: null, type: "skeleton", trees: {}, name: "", diff --git a/app/assets/javascripts/oxalis/view/action-bar/dataset_actions_view.js b/app/assets/javascripts/oxalis/view/action-bar/dataset_actions_view.js index 8a01722f4c4..cf73f700736 100644 --- a/app/assets/javascripts/oxalis/view/action-bar/dataset_actions_view.js +++ b/app/assets/javascripts/oxalis/view/action-bar/dataset_actions_view.js @@ -56,14 +56,14 @@ class DatasetActionsView extends PureComponent { handleCopyToAccount = async (event: SyntheticInputEvent) => { event.target.blur(); const url = `/annotations/${this.props.tracing.tracingType}/${this.props.tracing - .tracingId}/duplicate`; + .annotationId}/duplicate`; app.router.loadURL(url); }; handleFinish = async (event: SyntheticInputEvent) => { event.target.blur(); const url = `/annotations/${this.props.tracing.tracingType}/${this.props.tracing - .tracingId}/finishAndRedirect`; + .annotationId}/finishAndRedirect`; await this.handleSave(); if (confirm(messages["finish.confirm"])) { app.router.loadURL(url); diff --git a/app/assets/javascripts/oxalis/view/action-bar/merge_modal_view.js b/app/assets/javascripts/oxalis/view/action-bar/merge_modal_view.js index 595865acff8..1076401b14c 100644 --- a/app/assets/javascripts/oxalis/view/action-bar/merge_modal_view.js +++ b/app/assets/javascripts/oxalis/view/action-bar/merge_modal_view.js @@ -43,7 +43,7 @@ class MergeModalView extends PureComponent { props: { isVisible: boolean, onOk: () => void, - tracingId: string, + annotationId: string, tracingType: string, }; @@ -97,7 +97,7 @@ class MergeModalView extends PureComponent { Toast.message(info.file.response.messages); const url = `/annotations/${annotation.typ}/${annotation.id}/merge/` + - `${this.props.tracingType}/${this.props.tracingId}`; + `${this.props.tracingType}/${this.props.annotationId}`; this.merge(url); } }; @@ -108,7 +108,7 @@ class MergeModalView extends PureComponent { if (selectedTaskType != null) { const url = `/annotations/CompoundTaskType/${selectedTaskType}/` + - `merge/${this.props.tracingType}/${this.props.tracingId}`; + `merge/${this.props.tracingType}/${this.props.annotationId}`; this.merge(url); } }; @@ -119,7 +119,7 @@ class MergeModalView extends PureComponent { if (selectedProject != null) { const url = `/annotations/CompoundProject/${selectedProject}/merge/` + - `${this.props.tracingType}/${this.props.tracingId}`; + `${this.props.tracingType}/${this.props.annotationId}`; this.merge(url); } }; @@ -131,7 +131,7 @@ class MergeModalView extends PureComponent { if (selectedExplorativeAnnotation != null) { const url = `/annotations/Explorational/${selectedExplorativeAnnotation}/merge/` + - `${this.props.tracingType}/${this.props.tracingId}`; + `${this.props.tracingType}/${this.props.annotationId}`; this.merge(url); } }; @@ -236,7 +236,7 @@ class MergeModalView extends PureComponent { function mapStateToProps(state: OxalisState) { return { - tracingId: state.tracing.tracingId, + annotationId: state.tracing.annotationId, tracingType: state.tracing.tracingType, }; } diff --git a/app/assets/javascripts/oxalis/view/tracing_layout_view.js b/app/assets/javascripts/oxalis/view/tracing_layout_view.js index c161ab2c8af..5d2532a00b9 100644 --- a/app/assets/javascripts/oxalis/view/tracing_layout_view.js +++ b/app/assets/javascripts/oxalis/view/tracing_layout_view.js @@ -23,7 +23,7 @@ const { Header, Sider } = Layout; class TracingLayoutView extends React.PureComponent { props: { initialTracingType: SkeletonTracingTypeTracingType, - initialTracingId: string, + initialAnnotationId: string, initialControlmode: ControlModeType, }; @@ -69,7 +69,7 @@ class TracingLayoutView extends React.PureComponent {
{ app.oxalis = ref; diff --git a/app/assets/javascripts/router.js b/app/assets/javascripts/router.js index 3da38c1e09c..0efb456f832 100644 --- a/app/assets/javascripts/router.js +++ b/app/assets/javascripts/router.js @@ -79,7 +79,7 @@ class Router extends BaseRouter { tracingView(type, id) { const view = new ReactBackboneWrapper(TracingLayoutView, { initialTracingType: type, - initialTracingId: id, + initialAnnotationId: id, initialControlmode: ControlModeEnum.TRACE, }); view.forcePageReload = true; @@ -89,7 +89,7 @@ class Router extends BaseRouter { tracingViewPublic(id) { const view = new ReactBackboneWrapper(TracingLayoutView, { initialTracingType: SkeletonTracingTypeTracingEnum.View, - initialTracingId: id, + initialAnnotationId: id, initialControlmode: ControlModeEnum.VIEW, }); view.forcePageReload = true; diff --git a/app/assets/javascripts/test/api/api_skeleton_latest.spec.js b/app/assets/javascripts/test/api/api_skeleton_latest.spec.js index d6b1f351fcb..58333bfc891 100644 --- a/app/assets/javascripts/test/api/api_skeleton_latest.spec.js +++ b/app/assets/javascripts/test/api/api_skeleton_latest.spec.js @@ -88,7 +88,7 @@ test("Data Api: getBoundingBox should throw an error if the layer name is not va test("Data Api: getBoundingBox should get the bounding box of a layer", t => { const api = t.context.api; - const correctBoundingBox = [[3840, 4220, 2304], [3968, 4351, 2688]]; + const correctBoundingBox = [[0, 0, 0], [1024, 1024, 1024]]; const boundingBox = api.data.getBoundingBox("color"); t.deepEqual(boundingBox, correctBoundingBox); }); @@ -104,7 +104,7 @@ test("Data Api: getDataValue should get the data value for a layer, position and // There is another spec for pullqueue.js const { api, model } = t.context; const cube = model.getBinaryByName("segmentation").cube; - const position = [3840, 4220, 2304]; + const position = [100, 100, 100]; const zoomStep = 0; const bucketAddress = cube.positionToZoomedAddress(position, zoomStep); const bucket = cube.getOrCreateBucket(bucketAddress); diff --git a/app/assets/javascripts/test/api/api_v1.spec.js b/app/assets/javascripts/test/api/api_v1.spec.js index b3425698030..5a5725a20e7 100644 --- a/app/assets/javascripts/test/api/api_v1.spec.js +++ b/app/assets/javascripts/test/api/api_v1.spec.js @@ -1,94 +1,13 @@ /* eslint import/no-extraneous-dependencies: ["error", {"peerDependencies": true}] */ import test from "ava"; -import mockRequire from "mock-require"; import sinon from "sinon"; -import _ from "lodash"; -import Backbone from "backbone"; import "backbone.marionette"; -import { ControlModeEnum } from "oxalis/constants"; import { createNodeAction, deleteNodeAction } from "oxalis/model/actions/skeletontracing_actions"; -import TRACING_OBJECT from "../fixtures/skeletontracing_object"; - -function makeModelMock() { - class ModelMock {} - ModelMock.prototype.fetch = sinon.stub(); - ModelMock.prototype.fetch.returns(Promise.resolve()); - return ModelMock; -} - -const User = makeModelMock(); -const DatasetConfiguration = makeModelMock(); -const Request = { - receiveJSON: sinon.stub(), - sendJSONReceiveJSON: sinon.stub(), - sendArraybufferReceiveArraybuffer: sinon.stub(), - always: () => Promise.resolve(), -}; -const ErrorHandling = { - assertExtendContext: _.noop, - assertExists: _.noop, - assert: _.noop, -}; -const window = { - location: { - pathname: "annotationUrl", - }, - alert: console.log.bind(console), -}; -const currentUser = { - firstName: "SCM", - lastName: "Boy", -}; -const app = { - vent: Backbone.Radio.channel("global"), - currentUser, -}; -const KeyboardJS = { - bind: _.noop, - unbind: _.noop, -}; - -mockRequire("libs/toast", { error: _.noop }); -mockRequire("libs/window", window); -mockRequire("libs/request", Request); -mockRequire("libs/error_handling", ErrorHandling); -mockRequire("app", app); -mockRequire("oxalis/model/volumetracing/volumetracing", _.noop); -mockRequire("oxalis/model/user", User); -mockRequire("oxalis/model/dataset_configuration", DatasetConfiguration); -mockRequire("keyboardjs", KeyboardJS); - -// Avoid node caching and make sure all mockRequires are applied -const UrlManager = mockRequire.reRequire("oxalis/controller/url_manager").default; -const Model = mockRequire.reRequire("oxalis/model").OxalisModel; -const OxalisApi = mockRequire.reRequire("oxalis/api/api_loader").default; -const Store = mockRequire.reRequire("oxalis/store").default; - -test.beforeEach(t => { - UrlManager.initialState = { position: [1, 2, 3] }; - const model = new Model(); - t.context.model = model; - - const webknossos = new OxalisApi(model); - t.context.webknossos = webknossos; - - Request.receiveJSON.returns(Promise.resolve(_.cloneDeep(TRACING_OBJECT))); - User.prototype.fetch.returns(Promise.resolve()); - - return model - .fetch("tracingTypeValue", "tracingIdValue", ControlModeEnum.TRACE, true) - .then(() => { - // Trigger the event ourselves, as the OxalisController is not instantiated - app.vent.trigger("webknossos:ready"); - webknossos.apiReady(1).then(apiObject => { - t.context.api = apiObject; - }); - }) - .catch(error => { - console.error("model.fetch() failed", error); - t.fail(error.message); - }); -}); +import { setupOxalis, KeyboardJS } from "test/helpers/apiHelpers"; + +// All the mocking is done in the helpers file, so it can be reused for both skeleton and volume API +// Use API_VERSION 1 +test.beforeEach(t => setupOxalis(t, "skeleton", 1)); test("getActiveNodeId should get the active node id", t => { const api = t.context.api; @@ -165,7 +84,7 @@ test("getBoundingBox should throw an error if the layer name is not valid", t => test("getBoundingBox should get the bounding box of a layer", t => { const api = t.context.api; - const correctBoundingBox = [[3840, 4220, 2304], [3968, 4351, 2688]]; + const correctBoundingBox = [[0, 0, 0], [1024, 1024, 1024]]; const boundingBox = api.data.getBoundingBox("color"); t.deepEqual(boundingBox, correctBoundingBox); }); @@ -229,7 +148,7 @@ test("registerOverwrite should overwrite newAddNode", t => { }; api.utils.registerOverwrite("addNode", newAddNode); - Store.dispatch(createNodeAction([0, 0, 0], [0, 0, 0], 1, 1)); + t.context.Store.dispatch(createNodeAction([0, 0, 0], [0, 0, 0], 1, 1)); // The added instructions should have been executed t.true(bool); @@ -244,8 +163,8 @@ test("registerOverwrite should overwrite deleteActiveNode", t => { }; api.utils.registerOverwrite("deleteActiveNode", deleteNode); - Store.dispatch(createNodeAction([0, 0, 0], [0, 0, 0], 1, 1, 0)); - Store.dispatch(deleteNodeAction(0, 0)); + t.context.Store.dispatch(createNodeAction([0, 0, 0], [0, 0, 0], 1, 1, 0)); + t.context.Store.dispatch(deleteNodeAction(0, 0)); // The added instructions should have been executed t.true(bool); diff --git a/app/assets/javascripts/test/api/api_volume_latest.spec.js b/app/assets/javascripts/test/api/api_volume_latest.spec.js index d367901ec43..60b96e0394f 100644 --- a/app/assets/javascripts/test/api/api_volume_latest.spec.js +++ b/app/assets/javascripts/test/api/api_volume_latest.spec.js @@ -4,14 +4,14 @@ import "backbone.marionette"; import sinon from "sinon"; import Constants from "oxalis/constants"; import { setupOxalis } from "test/helpers/apiHelpers"; -import VOLUMETRACING_OBJECT from "../fixtures/volumetracing_object"; +import { tracing as TRACING } from "../fixtures/volumetracing_server_objects"; // All the mocking is done in the helpers file, so it can be reused for both skeleton and volume API test.beforeEach(t => setupOxalis(t, "volume")); test("getActiveCellId should get the id of the active cell", t => { const api = t.context.api; - t.is(api.tracing.getActiveCellId(), VOLUMETRACING_OBJECT.content.contentData.activeCell); + t.is(api.tracing.getActiveCellId(), TRACING.activeCell); }); test("setActiveCell should set the active cell id", t => { diff --git a/app/assets/javascripts/test/controller/url_manager.spec.js b/app/assets/javascripts/test/controller/url_manager.spec.js index 44335abe758..5af77faf587 100644 --- a/app/assets/javascripts/test/controller/url_manager.spec.js +++ b/app/assets/javascripts/test/controller/url_manager.spec.js @@ -8,21 +8,25 @@ const { updateTypeAndId } = mockRequire.reRequire("oxalis/controller/url_manager test("UrlManager should replace tracing in url", t => { t.is( - updateTypeAndId("abc/def/annotations/tracingType/tracingId", "newTracingType", "newTracingId"), - "abc/def/annotations/newTracingType/newTracingId", + updateTypeAndId( + "abc/def/annotations/tracingType/annotationId", + "newTracingType", + "newAnnotationId", + ), + "abc/def/annotations/newTracingType/newAnnotationId", ); t.is( updateTypeAndId( - "abc/def/annotations/tracingType/tracingId/readOnly", + "abc/def/annotations/tracingType/annotationId/readOnly", "newTracingType", - "newTracingId", + "newAnnotationId", ), - "abc/def/annotations/newTracingType/newTracingId/readOnly", + "abc/def/annotations/newTracingType/newAnnotationId/readOnly", ); t.is( - updateTypeAndId("abc/def/datasets/tracingId/view/rest", "newTracingType", "newTracingId"), - "abc/def/datasets/newTracingId/view/rest", + updateTypeAndId("abc/def/datasets/annotationId/view/rest", "newTracingType", "newAnnotationId"), + "abc/def/datasets/newAnnotationId/view/rest", ); }); diff --git a/app/assets/javascripts/test/fixtures/dataset_server_object.js b/app/assets/javascripts/test/fixtures/dataset_server_object.js new file mode 100644 index 00000000000..7340e6b3004 --- /dev/null +++ b/app/assets/javascripts/test/fixtures/dataset_server_object.js @@ -0,0 +1,40 @@ +export default { + name: "ROI2017_wkw", + dataSource: { + id: { name: "ROI2017_wkw", team: "Connectomics department" }, + dataLayers: [ + { + name: "color", + category: "color", + boundingBox: { topLeft: [0, 0, 0], width: 1024, height: 1024, depth: 1024 }, + resolutions: [1, 16, 2, 32, 4, 8], + elementClass: "uint8", + }, + { + name: "segmentation", + category: "segmentation", + boundingBox: { topLeft: [0, 0, 0], width: 1024, height: 1024, depth: 1024 }, + resolutions: [1, 16, 2, 32, 4, 8], + elementClass: "uint32", + largestSegmentId: 1000000000, + mappings: [ + "larger5um1", + "axons", + "astrocyte-ge-7", + "astrocyte", + "mitochondria", + "astrocyte-full", + ], + }, + ], + scale: [11.239999771118164, 11.239999771118164, 28], + }, + dataStore: { name: "localhost", url: "http://localhost:9000", typ: "webknossos-store" }, + owningTeam: "Connectomics department", + allowedTeams: ["Connectomics department"], + isActive: true, + isPublic: false, + description: null, + created: 1502288550432, + isEditable: true, +}; diff --git a/app/assets/javascripts/test/fixtures/skeletontracing_object.js b/app/assets/javascripts/test/fixtures/skeletontracing_object.js deleted file mode 100644 index 2aa744dd6b5..00000000000 --- a/app/assets/javascripts/test/fixtures/skeletontracing_object.js +++ /dev/null @@ -1,182 +0,0 @@ -export default { - version: 2, - user: { - id: "573f27ef3e00005400e69659", - email: "scmboy@scalableminds.com", - firstName: "SCM", - lastName: "Boy", - isAnonymous: false, - teams: [ - { - team: "another", - role: { - name: "admin", - }, - }, - ], - }, - created: "2017-02-08 14:23", - stateLabel: "In Progress", - state: { - isAssigned: true, - isFinished: false, - isInProgress: true, - }, - id: "589b1bda4000009803e96ebc", - name: "", - typ: "Explorational", - task: null, - stats: { - numberOfNodes: 3, - numberOfEdges: 1, - numberOfTrees: 2, - }, - restrictions: { - allowAccess: true, - allowUpdate: true, - allowFinish: true, - allowDownload: true, - }, - formattedHash: "e96ebc", - downloadUrl: "http://localhost:9000/annotations/Explorational/589b1bda4000009803e96ebc/download", - content: { - settings: { - allowedModes: ["orthogonal", "oblique", "flight"], - branchPointsAllowed: true, - somaClickingAllowed: true, - advancedOptionsAllowed: true, - }, - dataSet: { - name: "2012-09-28_ex145_07x2", - dataStore: { - name: "localhost", - url: "http://localhost:9000", - typ: "webknossos-store", - }, - scale: [16.5, 16.5, 25], - dataLayers: [ - { - name: "color", - category: "color", - boundingBox: { - topLeft: [3840, 4220, 2304], - width: 128, - height: 131, - depth: 384, - }, - resolutions: [1, 2, 4, 8], - fallback: null, - elementClass: "uint8", - mappings: [], - }, - { - name: "segmentation", - category: "segmentation", - boundingBox: { - topLeft: [3840, 4220, 2304], - width: 128, - height: 131, - depth: 384, - }, - resolutions: [1], - fallback: null, - elementClass: "uint16", - mappings: [ - { - name: "mapping_1", - path: "mappingPath", - }, - ], - }, - ], - }, - contentData: { - activeNode: 3, - trees: [ - { - id: 1, - nodes: [ - { - id: 1, - radius: 165, - position: [3903, 4283, 2496], - rotation: [0, 0, 0], - viewport: 0, - resolution: 0, - bitDepth: 8, - interpolation: false, - timestamp: 1486560222916, - }, - { - id: 2, - radius: 165, - position: [3914, 4277, 2496], - rotation: [0, 0, 0], - viewport: 0, - resolution: 0, - bitDepth: 8, - interpolation: false, - timestamp: 1486560224706, - }, - ], - edges: [ - { - source: 1, - target: 2, - }, - ], - name: "explorative_2017-02-08_SCM_Boy_001", - color: [0, 0.2901961, 1, 1], - timestamp: 1486560222916, - comments: [], - branchPoints: [ - { - id: 1, - timestamp: 1486560222916, - }, - ], - }, - { - id: 2, - nodes: [ - { - id: 3, - radius: 165, - position: [3918, 4292, 2496], - rotation: [0, 0, 0], - viewport: 0, - resolution: 0, - bitDepth: 8, - interpolation: false, - timestamp: 1486560227441, - }, - ], - edges: [], - name: "explorative_2017-02-08_SCM_Boy_002", - color: [0.5803922, 1, 0, 1], - timestamp: 1486560227441, - comments: [ - { - content: "Test", - node: 3, - }, - ], - branchPoints: [ - { - id: 3, - timestamp: 1486560227441, - }, - ], - }, - ], - zoomLevel: -1.4, - }, - editPosition: [3918, 4292, 2496], - editRotation: [0, 0, 0], - boundingBox: null, - contentType: "skeleton", - }, - contentType: "skeleton", - dataSetName: "2012-09-28_ex145_07x2", - tracingTime: 9702, -}; diff --git a/app/assets/javascripts/test/fixtures/skeletontracing_server_objects.js b/app/assets/javascripts/test/fixtures/skeletontracing_server_objects.js new file mode 100644 index 00000000000..929ea5cbb04 --- /dev/null +++ b/app/assets/javascripts/test/fixtures/skeletontracing_server_objects.js @@ -0,0 +1,87 @@ +export const tracing = { + id: "47e37793-d0be-4240-a371-87ce68561a13", + dataSetName: "ROI2017_wkw", + trees: [ + { + treeId: 2, + nodes: [ + { + id: 3, + position: [138, 22, 0], + rotation: [0, 0, 0], + radius: 112.39999389648438, + viewport: 0, + resolution: 1, + bitDepth: 4, + interpolation: true, + timestamp: 1502302785450, + }, + ], + edges: [], + color: [0, 0, 1], + branchPoints: [{ id: 3, timestamp: 1502302770510 }], + comments: [{ node: 3, content: "Test" }], + name: "explorative_2017-08-09_SCM_Boy_002", + }, + { + treeId: 1, + nodes: [ + { + id: 1, + position: [24, 32, 0], + rotation: [0, 0, 0], + radius: 112.39999389648438, + viewport: 0, + resolution: 1, + bitDepth: 4, + interpolation: true, + timestamp: 1502302785447, + }, + { + id: 2, + position: [104, 106, 0], + rotation: [0, 0, 0], + radius: 112.39999389648438, + viewport: 0, + resolution: 1, + bitDepth: 4, + interpolation: true, + timestamp: 1502302785448, + }, + ], + edges: [{ source: 1, target: 2 }], + color: [0.6784313917160034, 0.1411764770746231, 0.05098039284348488], + branchPoints: [{ id: 1, timestamp: 1502302774534 }], + comments: [], + name: "explorative_2017-08-09_SCM_Boy_001", + }, + ], + timestamp: 1502302761387, + activeNodeId: 3, + editPosition: [24, 32, 0], + editRotation: [79.99999570976581, 73.99999869555745, 4.908922051072295e-7], + zoomLevel: 2, + version: 7, +}; + +export const annotation = { + created: "2017-08-09 20:19", + state: { isAssigned: true, isFinished: false, isInProgress: true }, + id: "598b52293c00009906f043e7", + name: "", + typ: "Explorational", + task: null, + stats: null, + restrictions: { allowAccess: true, allowUpdate: true, allowFinish: true, allowDownload: true }, + formattedHash: "f043e7", + content: { id: "47e37793-d0be-4240-a371-87ce68561a13", typ: "skeleton" }, + dataSetName: "ROI2017_wkw", + dataStore: { name: "localhost", url: "http://localhost:9000", typ: "webknossos-store" }, + settings: { + allowedModes: ["orthogonal", "oblique", "flight"], + branchPointsAllowed: true, + somaClickingAllowed: true, + advancedOptionsAllowed: true, + }, + tracingTime: 0, +}; diff --git a/app/assets/javascripts/test/fixtures/volumetracing_object.js b/app/assets/javascripts/test/fixtures/volumetracing_object.js deleted file mode 100644 index 0043ff1c70a..00000000000 --- a/app/assets/javascripts/test/fixtures/volumetracing_object.js +++ /dev/null @@ -1,121 +0,0 @@ -export default { - version: 3, - user: { - id: "573f27ef3e00005400e69659", - email: "scmboy@scalableminds.com", - firstName: "SCM", - lastName: "Boy", - isAnonymous: false, - teams: [ - { - team: "another", - role: { - name: "admin", - }, - }, - ], - }, - created: "2017-05-03 15:17", - stateLabel: "In Progress", - state: { - isAssigned: true, - isFinished: false, - isInProgress: true, - }, - id: "5909d8793e00008c029d4d2a", - name: "", - typ: "Explorational", - task: null, - stats: null, - restrictions: { - allowAccess: true, - allowUpdate: true, - allowFinish: true, - allowDownload: true, - }, - formattedHash: "9d4d2a", - downloadUrl: "http://localhost:9000/annotations/Explorational/5909d8793e00008c029d4d2a/download", - content: { - settings: { - allowedModes: ["volume"], - branchPointsAllowed: true, - somaClickingAllowed: true, - advancedOptionsAllowed: true, - }, - dataSet: { - name: "2012-09-28_ex145_07x2", - dataStore: { - name: "localhost", - url: "http://localhost:9000", - typ: "webknossos-store", - }, - scale: [16.5, 16.5, 25], - dataLayers: [ - { - name: "color", - category: "color", - boundingBox: { - topLeft: [3840, 4220, 2304], - width: 128, - height: 131, - depth: 384, - }, - resolutions: [1, 2, 4, 8], - fallback: null, - elementClass: "uint8", - mappings: [], - }, - { - name: "segmentation", - category: "segmentation", - boundingBox: { - topLeft: [3840, 4220, 2304], - width: 128, - height: 131, - depth: 384, - }, - resolutions: [1], - fallback: null, - elementClass: "uint16", - mappings: [], - }, - ], - }, - contentData: { - activeCell: 10000, - customLayers: [ - { - name: "64007765-cef9-4e31-b206-dba795b5be17", - category: "segmentation", - boundingBox: { - topLeft: [3840, 4220, 2304], - width: 128, - height: 131, - depth: 384, - }, - resolutions: [1], - fallback: { - dataSourceName: "2012-09-28_ex145_07x2", - layerName: "segmentation", - }, - elementClass: "uint16", - mappings: [ - { - name: "mapping_1", - path: "mapping_path", - }, - ], - }, - ], - nextCell: 21890, - zoomLevel: 0, - }, - editPosition: [3904, 4282, 2496], - editRotation: [0, 0, 0], - boundingBox: null, - contentType: "volume", - }, - contentType: "volume", - dataSetName: "2012-09-28_ex145_07x2", - tracingTime: 76252, -}; diff --git a/app/assets/javascripts/test/fixtures/volumetracing_server_objects.js b/app/assets/javascripts/test/fixtures/volumetracing_server_objects.js new file mode 100644 index 00000000000..e81ab001b8e --- /dev/null +++ b/app/assets/javascripts/test/fixtures/volumetracing_server_objects.js @@ -0,0 +1,55 @@ +export const tracing = { + activeCell: 10000, + customLayers: [ + { + name: "64007765-cef9-4e31-b206-dba795b5be17", + category: "segmentation", + boundingBox: { + topLeft: [3840, 4220, 2304], + width: 128, + height: 131, + depth: 384, + }, + resolutions: [1], + fallback: { + dataSourceName: "2012-09-28_ex145_07x2", + layerName: "segmentation", + }, + elementClass: "uint16", + mappings: [ + { + name: "mapping_1", + path: "mapping_path", + }, + ], + }, + ], + nextCell: 21890, + zoomLevel: 0, + editPosition: [3904, 4282, 2496], + editRotation: [0, 0, 0], + boundingBox: null, + contentType: "volume", +}; + +export const annotation = { + created: "2017-08-09 20:19", + state: { isAssigned: true, isFinished: false, isInProgress: true }, + id: "598b52293c00009906f043e7", + name: "", + typ: "Explorational", + task: null, + stats: null, + restrictions: { allowAccess: true, allowUpdate: true, allowFinish: true, allowDownload: true }, + formattedHash: "f043e7", + content: { id: "47e37793-d0be-4240-a371-87ce68561a13", typ: "volume" }, + dataSetName: "ROI2017_wkw", + dataStore: { name: "localhost", url: "http://localhost:9000", typ: "webknossos-store" }, + settings: { + allowedModes: ["volume"], + branchPointsAllowed: true, + somaClickingAllowed: true, + advancedOptionsAllowed: true, + }, + tracingTime: 0, +}; diff --git a/app/assets/javascripts/test/helpers/apiHelpers.js b/app/assets/javascripts/test/helpers/apiHelpers.js index 852a2d5a1ab..7c6aadb6df5 100644 --- a/app/assets/javascripts/test/helpers/apiHelpers.js +++ b/app/assets/javascripts/test/helpers/apiHelpers.js @@ -4,8 +4,15 @@ import mockRequire from "mock-require"; import sinon from "sinon"; import _ from "lodash"; import { ControlModeEnum } from "oxalis/constants"; -import SKELETONTRACING_OBJECT from "../fixtures/skeletontracing_object"; -import VOLUMETRACING_OBJECT from "../fixtures/volumetracing_object"; +import { + tracing as SKELETON_TRACING, + annotation as SKELETON_ANNOTATION, +} from "../fixtures/skeletontracing_server_objects"; +import { + tracing as VOLUME_TRACING, + annotation as VOLUME_ANNOTATION, +} from "../fixtures/volumetracing_server_objects"; +import DATASET from "../fixtures/dataset_server_object"; const Request = { receiveJSON: sinon.stub(), @@ -37,38 +44,61 @@ export const KeyboardJS = { unbind: _.noop, }; mockRequire("keyboardjs", KeyboardJS); -mockRequire("libs/toast", { error: _.noop }); +mockRequire("libs/toast", { error: _.noop, warning: _.noop }); mockRequire("libs/window", window); mockRequire("libs/request", Request); mockRequire("libs/error_handling", ErrorHandling); mockRequire("app", app); -mockRequire("oxalis/model/volumetracing/volumetracing", _.noop); // Avoid node caching and make sure all mockRequires are applied const UrlManager = mockRequire.reRequire("oxalis/controller/url_manager").default; const Model = mockRequire.reRequire("oxalis/model").OxalisModel; const OxalisApi = mockRequire.reRequire("oxalis/api/api_loader").default; +const Store = mockRequire.reRequire("oxalis/store").default; const modelData = { - skeleton: SKELETONTRACING_OBJECT, - volume: VOLUMETRACING_OBJECT, + skeleton: { + tracing: SKELETON_TRACING, + annotation: SKELETON_ANNOTATION, + }, + volume: { + tracing: VOLUME_TRACING, + annotation: VOLUME_ANNOTATION, + }, }; -export function setupOxalis(t, mode) { +const TRACING_TYPE = "tracingTypeValue"; +const ANNOTATION_ID = "annotationIdValue"; + +export function setupOxalis(t, mode, apiVersion = 2) { UrlManager.initialState = { position: [1, 2, 3] }; const model = new Model(); t.context.model = model; + t.context.Store = Store; const webknossos = new OxalisApi(model); - Request.receiveJSON.returns(Promise.resolve(_.cloneDeep(modelData[mode]))); + const ANNOTATION = modelData[mode].annotation; + Request.receiveJSON + .withArgs(`/annotations/${TRACING_TYPE}/${ANNOTATION_ID}/info`) + .returns(Promise.resolve(_.cloneDeep(ANNOTATION))); + Request.receiveJSON + .withArgs(`/api/datasets/${ANNOTATION.dataSetName}`) + .returns(Promise.resolve(_.cloneDeep(DATASET))); + Request.receiveJSON + .withArgs( + `${ANNOTATION.dataStore.url}/data/tracings/${ANNOTATION.content.typ}/${ANNOTATION.content + .id}`, + ) + .returns(Promise.resolve(_.cloneDeep(modelData[mode].tracing))); + Request.receiveJSON.returns(Promise.resolve({})); return model - .fetch("tracingTypeValue", "tracingIdValue", ControlModeEnum.TRACE, true) + .fetch(TRACING_TYPE, ANNOTATION_ID, ControlModeEnum.TRACE, true) .then(() => { // Trigger the event ourselves, as the OxalisController is not instantiated app.vent.trigger("webknossos:ready"); - webknossos.apiReady(2).then(apiObject => { + webknossos.apiReady(apiVersion).then(apiObject => { t.context.api = apiObject; }); }) diff --git a/app/assets/javascripts/test/model/model.spec.js b/app/assets/javascripts/test/model/model.spec.js index abc82d23f4f..9aa4a127844 100644 --- a/app/assets/javascripts/test/model/model.spec.js +++ b/app/assets/javascripts/test/model/model.spec.js @@ -3,6 +3,11 @@ import test from "ava"; import mockRequire from "mock-require"; import sinon from "sinon"; import _ from "lodash"; +import DATASET from "../fixtures/dataset_server_object"; +import { + tracing as TRACING, + annotation as ANNOTATION, +} from "../fixtures/skeletontracing_server_objects"; mockRequire.stopAll(); @@ -43,44 +48,29 @@ mockRequire("oxalis/model/binary/layers/wk_layer", Layer); mockRequire("oxalis/model/binary/layers/nd_store_layer", Layer); mockRequire("libs/window", {}); -const TRACING_OBJECT = { - content: { - dataSet: { - name: "DatasetName", - dataStore: { - url: "dataStoreUrl", - typ: "webknossos-store", - }, - dataLayers: [ - { - name: "layer1", - category: "color", - elementClass: "Uint32", - resolutions: [1], - }, - ], - }, - settings: { - allowedModes: [], - }, - contentData: { - customLayers: [], - }, - }, -}; - // Avoid node caching and make sure all mockRequires are applied const Model = mockRequire.reRequire("../../oxalis/model").OxalisModel; +const TRACING_TYPE = "tracingTypeValue"; +const ANNOTATION_ID = "annotationIdValue"; + test.beforeEach(t => { - const model = new Model({ - tracingType: "tracingTypeValue", - tracingId: "tracingIdValue", - }); + const model = new Model(); t.context.model = model; model.state = { position: [1, 2, 3] }; - Request.receiveJSON.returns(Promise.resolve(TRACING_OBJECT)); + Request.receiveJSON + .withArgs(`/annotations/${TRACING_TYPE}/${ANNOTATION_ID}/info`) + .returns(Promise.resolve(_.cloneDeep(ANNOTATION))); + Request.receiveJSON + .withArgs(`/api/datasets/${ANNOTATION.dataSetName}`) + .returns(Promise.resolve(_.cloneDeep(DATASET))); + Request.receiveJSON + .withArgs( + `${ANNOTATION.dataStore.url}/data/tracings/${ANNOTATION.content.typ}/${ANNOTATION.content + .id}`, + ) + .returns(Promise.resolve(_.cloneDeep(TRACING))); User.prototype.fetch.returns(Promise.resolve()); }); @@ -96,15 +86,17 @@ test.beforeEach(t => { // }); // }); -test("Model Initialization: should throw a model.HANDLED_ERROR for missing dataset", t => { +test("Model Initialization: should throw a model.HANDLED_ERROR for missing data layers", t => { t.plan(1); const { model } = t.context; - const tracingObject = _.clone(TRACING_OBJECT); - delete tracingObject.content.dataSet; - Request.receiveJSON.returns(Promise.resolve(tracingObject)); + const datasetObject = _.clone(DATASET); + delete datasetObject.dataSource.dataLayers; + Request.receiveJSON + .withArgs(`/api/datasets/${ANNOTATION.dataSetName}`) + .returns(Promise.resolve(_.cloneDeep(datasetObject))); return model - .fetch() + .fetch(TRACING_TYPE, ANNOTATION_ID, "VIEW", true) .then(() => { t.fail("Promise should not have been resolved."); }) @@ -116,10 +108,12 @@ test("Model Initialization: should throw a model.HANDLED_ERROR for missing datas test("Model Initialization: should throw an Error on unexpected failure", t => { t.plan(1); const { model } = t.context; - Request.receiveJSON.returns(Promise.reject(new Error("errorMessage"))); + Request.receiveJSON + .withArgs(`/annotations/${TRACING_TYPE}/${ANNOTATION_ID}/info`) + .returns(Promise.reject(new Error("errorMessage"))); return model - .fetch() + .fetch(TRACING_TYPE, ANNOTATION_ID, "VIEW", true) .then(() => { t.fail("Promise should not have been resolved."); }) diff --git a/app/assets/javascripts/test/sagas/save_saga.spec.js b/app/assets/javascripts/test/sagas/save_saga.spec.js index 289080800a2..527e6d270b8 100644 --- a/app/assets/javascripts/test/sagas/save_saga.spec.js +++ b/app/assets/javascripts/test/sagas/save_saga.spec.js @@ -71,6 +71,7 @@ const initialState = { const INIT_ACTIONS = ["INITIALIZE_SKELETONTRACING", "INITIALIZE_VOLUMETRACING"]; const LAST_VERSION = 2; +const DATASTORE_URL = "test.webknossos.xyz"; test("SaveSaga should compact multiple updateTracing update actions", t => { const saveQueue = createSaveQueueFromUpdateActions( @@ -110,12 +111,13 @@ test("SaveSaga should send request to server", t => { const saga = sendRequestToServer(TIMESTAMP); saga.next(); saga.next(saveQueue); + saga.next({ version: LAST_VERSION, type: "skeleton", tracingId: "1234567890" }); const saveQueueWithVersions = addVersionNumbers(saveQueue, LAST_VERSION); expectValueDeepEqual( t, - saga.next({ version: LAST_VERSION, tracingType: "Explorational", tracingId: "1234567890" }), - call(Request.sendJSONReceiveJSON, "/annotations/Explorational/1234567890", { - method: "PUT", + saga.next(DATASTORE_URL), + call(Request.sendJSONReceiveJSON, `${DATASTORE_URL}/data/tracings/skeleton/1234567890/update`, { + method: "POST", headers: { "X-Date": TIMESTAMP }, data: saveQueueWithVersions, }), @@ -131,12 +133,13 @@ test("SaveSaga should retry update actions", t => { const saga = sendRequestToServer(TIMESTAMP); saga.next(); saga.next(saveQueue); + saga.next({ version: LAST_VERSION, type: "skeleton", tracingId: "1234567890" }); const saveQueueWithVersions = addVersionNumbers(saveQueue, LAST_VERSION); expectValueDeepEqual( t, - saga.next({ version: LAST_VERSION, tracingType: "Explorational", tracingId: "1234567890" }), - call(Request.sendJSONReceiveJSON, "/annotations/Explorational/1234567890", { - method: "PUT", + saga.next(DATASTORE_URL), + call(Request.sendJSONReceiveJSON, `${DATASTORE_URL}/data/tracings/skeleton/1234567890/update`, { + method: "POST", headers: { "X-Date": TIMESTAMP }, data: saveQueueWithVersions, }), @@ -158,12 +161,13 @@ test("SaveSaga should escalate on permanent client error update actions", t => { const saga = sendRequestToServer(TIMESTAMP); saga.next(); saga.next(saveQueue); + saga.next({ version: LAST_VERSION, type: "skeleton", tracingId: "1234567890" }); const saveQueueWithVersions = addVersionNumbers(saveQueue, LAST_VERSION); expectValueDeepEqual( t, - saga.next({ version: LAST_VERSION, tracingType: "Explorational", tracingId: "1234567890" }), - call(Request.sendJSONReceiveJSON, "/annotations/Explorational/1234567890", { - method: "PUT", + saga.next(DATASTORE_URL), + call(Request.sendJSONReceiveJSON, `${DATASTORE_URL}/data/tracings/skeleton/1234567890/update`, { + method: "POST", headers: { "X-Date": TIMESTAMP }, data: saveQueueWithVersions, }), @@ -201,7 +205,8 @@ test("SaveSaga should remove the correct update actions", t => { const saga = sendRequestToServer(); saga.next(); saga.next(saveQueue); - saga.next({ version: LAST_VERSION, tracingType: "Explorational", tracingId: "1234567890" }); + saga.next({ version: LAST_VERSION, type: "skeleton", tracingId: "1234567890" }); + saga.next(DATASTORE_URL); expectValueDeepEqual(t, saga.next(), put(SaveActions.setVersionNumberAction(3))); expectValueDeepEqual(t, saga.next(), put(SaveActions.setLastSaveTimestampAction())); expectValueDeepEqual(t, saga.next(), put(SaveActions.shiftSaveQueueAction(2))); @@ -220,7 +225,8 @@ test("SaveSaga should set the correct version numbers", t => { const saga = sendRequestToServer(); saga.next(); saga.next(saveQueue); - saga.next({ version: LAST_VERSION, tracingType: "Explorational", tracingId: "1234567890" }); + saga.next({ version: LAST_VERSION, type: "skeleton", tracingId: "1234567890" }); + saga.next(DATASTORE_URL); expectValueDeepEqual(t, saga.next(), put(SaveActions.setVersionNumberAction(LAST_VERSION + 3))); expectValueDeepEqual(t, saga.next(), put(SaveActions.setLastSaveTimestampAction())); expectValueDeepEqual(t, saga.next(), put(SaveActions.shiftSaveQueueAction(3))); @@ -239,7 +245,8 @@ test("SaveSaga should set the correct version numbers if the save queue was comp const saga = sendRequestToServer(); saga.next(); saga.next(saveQueue); - saga.next({ version: LAST_VERSION, tracingType: "Explorational", tracingId: "1234567890" }); + saga.next({ version: LAST_VERSION, type: "skeleton", tracingId: "1234567890" }); + saga.next(DATASTORE_URL); // two of the updateTracing update actions are removed by compactUpdateActions expectValueDeepEqual(t, saga.next(), put(SaveActions.setVersionNumberAction(LAST_VERSION + 1))); expectValueDeepEqual(t, saga.next(), put(SaveActions.setLastSaveTimestampAction()));