diff --git a/platform/commonUI/browse/bundle.js b/platform/commonUI/browse/bundle.js index 443660cc8b..eccf3e96d7 100644 --- a/platform/commonUI/browse/bundle.js +++ b/platform/commonUI/browse/bundle.js @@ -27,6 +27,7 @@ define([ "./src/MenuArrowController", "./src/navigation/NavigationService", "./src/navigation/NavigateAction", + "./src/navigation/OrphanNavigationHandler", "./src/windowing/NewTabAction", "./src/windowing/FullscreenAction", "./src/windowing/WindowTitler", @@ -47,6 +48,7 @@ define([ MenuArrowController, NavigationService, NavigateAction, + OrphanNavigationHandler, NewTabAction, FullscreenAction, WindowTitler, @@ -253,6 +255,14 @@ define([ "$rootScope", "$document" ] + }, + { + "implementation": OrphanNavigationHandler, + "depends": [ + "throttle", + "topic", + "navigationService" + ] } ], "licenses": [ diff --git a/platform/commonUI/browse/src/navigation/OrphanNavigationHandler.js b/platform/commonUI/browse/src/navigation/OrphanNavigationHandler.js new file mode 100644 index 0000000000..00b3182e42 --- /dev/null +++ b/platform/commonUI/browse/src/navigation/OrphanNavigationHandler.js @@ -0,0 +1,75 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define([], function () { + + /** + * Navigates away from orphan objects whenever they are detected. + * + * An orphan object is an object whose apparent parent does not + * actually contain it. This may occur in certain circumstances, such + * as when persistence succeeds for a newly-created object but fails + * for its parent. + * + * @param throttle the `throttle` service + * @param topic the `topic` service + * @param navigationService the `navigationService` + * @constructor + */ + function OrphanNavigationHandler(throttle, topic, navigationService) { + var throttledCheckNavigation; + + function getParent(domainObject) { + var context = domainObject.getCapability('context'); + return context.getParent(); + } + + function isOrphan(domainObject) { + var parent = getParent(domainObject), + composition = parent.getModel().composition, + id = domainObject.getId(); + return !composition || (composition.indexOf(id) === -1); + } + + function navigateToParent(domainObject) { + var parent = getParent(domainObject); + return parent.getCapability('action').perform('navigate'); + } + + function checkNavigation() { + var navigatedObject = navigationService.getNavigation(); + if (navigatedObject.hasCapability('context') && + isOrphan(navigatedObject)) { + if (!navigatedObject.getCapability('editor').isEditContextRoot()) { + navigateToParent(navigatedObject); + } + } + } + + throttledCheckNavigation = throttle(checkNavigation); + + navigationService.addListener(throttledCheckNavigation); + topic('mutation').listen(throttledCheckNavigation); + } + + return OrphanNavigationHandler; +}); diff --git a/platform/commonUI/browse/test/navigation/OrphanNavigationHandlerSpec.js b/platform/commonUI/browse/test/navigation/OrphanNavigationHandlerSpec.js new file mode 100644 index 0000000000..4f71feedbd --- /dev/null +++ b/platform/commonUI/browse/test/navigation/OrphanNavigationHandlerSpec.js @@ -0,0 +1,180 @@ +/***************************************************************************** +* Open MCT Web, Copyright (c) 2014-2015, United States Government +* as represented by the Administrator of the National Aeronautics and Space +* Administration. All rights reserved. +* +* Open MCT Web is licensed under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0. +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +* +* Open MCT Web includes source code licensed under additional open source +* licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available +* at runtime from the About dialog for additional information. +*****************************************************************************/ + +define([ + '../../src/navigation/OrphanNavigationHandler' +], function (OrphanNavigationHandler) { + describe("OrphanNavigationHandler", function () { + var mockTopic, + mockThrottle, + mockMutationTopic, + mockNavigationService, + mockDomainObject, + mockParentObject, + mockContext, + mockActionCapability, + mockEditor, + testParentModel, + testId, + mockThrottledFns; + + beforeEach(function () { + testId = 'some-identifier'; + + mockThrottledFns = []; + testParentModel = {}; + + mockTopic = jasmine.createSpy('topic'); + mockThrottle = jasmine.createSpy('throttle'); + mockNavigationService = jasmine.createSpyObj('navigationService', [ + 'getNavigation', + 'addListener' + ]); + mockMutationTopic = jasmine.createSpyObj('mutationTopic', [ + 'listen' + ]); + mockDomainObject = jasmine.createSpyObj('domainObject', [ + 'getId', + 'getCapability', + 'getModel', + 'hasCapability' + ]); + mockParentObject = jasmine.createSpyObj('domainObject', [ + 'getId', + 'getCapability', + 'getModel', + 'hasCapability' + ]); + mockContext = jasmine.createSpyObj('context', ['getParent']); + mockActionCapability = jasmine.createSpyObj('action', ['perform']); + mockEditor = jasmine.createSpyObj('editor', ['isEditContextRoot']); + + mockThrottle.andCallFake(function (fn) { + var mockThrottledFn = + jasmine.createSpy('throttled-' + mockThrottledFns.length); + mockThrottledFn.andCallFake(fn); + mockThrottledFns.push(mockThrottledFn); + return mockThrottledFn; + }); + mockTopic.andCallFake(function (k) { + return k === 'mutation' && mockMutationTopic; + }); + mockDomainObject.getId.andReturn(testId); + mockDomainObject.getCapability.andCallFake(function (c) { + return { + context: mockContext, + editor: mockEditor + }[c]; + }); + mockDomainObject.hasCapability.andCallFake(function (c) { + return !!mockDomainObject.getCapability(c); + }); + mockParentObject.getModel.andReturn(testParentModel); + mockParentObject.getCapability.andCallFake(function (c) { + return { + action: mockActionCapability + }[c]; + }); + mockContext.getParent.andReturn(mockParentObject); + mockNavigationService.getNavigation.andReturn(mockDomainObject); + mockEditor.isEditContextRoot.andReturn(false); + + return new OrphanNavigationHandler( + mockThrottle, + mockTopic, + mockNavigationService + ); + }); + + + it("listens for mutation with a throttled function", function () { + expect(mockMutationTopic.listen) + .toHaveBeenCalledWith(jasmine.any(Function)); + expect(mockThrottledFns.indexOf( + mockMutationTopic.listen.mostRecentCall.args[0] + )).not.toEqual(-1); + }); + + it("listens for navigation changes with a throttled function", function () { + expect(mockNavigationService.addListener) + .toHaveBeenCalledWith(jasmine.any(Function)); + expect(mockThrottledFns.indexOf( + mockNavigationService.addListener.mostRecentCall.args[0] + )).not.toEqual(-1); + }); + + [false, true].forEach(function (isOrphan) { + var prefix = isOrphan ? "" : "non-"; + describe("for " + prefix + "orphan objects", function () { + beforeEach(function () { + testParentModel.composition = isOrphan ? [] : [testId]; + }); + + [false, true].forEach(function (isEditRoot) { + var caseName = isEditRoot ? + "that are being edited" : "that are not being edited"; + + function itNavigatesAsExpected() { + if (isOrphan && !isEditRoot) { + it("navigates to the parent", function () { + expect(mockActionCapability.perform) + .toHaveBeenCalledWith('navigate'); + }); + } else { + it("does nothing", function () { + expect(mockActionCapability.perform) + .not.toHaveBeenCalled(); + }); + } + } + + describe(caseName, function () { + beforeEach(function () { + mockEditor.isEditContextRoot.andReturn(isEditRoot); + }); + + describe("when navigation changes", function () { + beforeEach(function () { + mockNavigationService.addListener.mostRecentCall + .args[0](mockDomainObject); + }); + + itNavigatesAsExpected(); + }); + + describe("when mutation occurs", function () { + beforeEach(function () { + mockMutationTopic.listen.mostRecentCall + .args[0](mockParentObject); + }); + + itNavigatesAsExpected(); + }); + + }); + }); + }); + }); + + }); +}); + diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js index a07c5bb230..b8906107a0 100644 --- a/platform/commonUI/edit/bundle.js +++ b/platform/commonUI/edit/bundle.js @@ -253,7 +253,7 @@ define([ }, { "category": "navigation", - "message": "There are unsaved changes.", + "message": "Continuing will cause the loss of any unsaved changes.", "implementation": EditNavigationPolicy }, { diff --git a/platform/commonUI/general/res/sass/user-environ/_layout.scss b/platform/commonUI/general/res/sass/user-environ/_layout.scss index 9e8e9aaae1..8537c85520 100644 --- a/platform/commonUI/general/res/sass/user-environ/_layout.scss +++ b/platform/commonUI/general/res/sass/user-environ/_layout.scss @@ -288,8 +288,9 @@ body.desktop .pane .mini-tab-icon.toggle-pane { .left { padding-right: $interiorMarginLg; - .l-back:not(.s-status-editing) { + .l-back { margin-right: $interiorMarginLg; + &.s-status-editing { display: none; } } } } diff --git a/platform/features/timeline/bundle.js b/platform/features/timeline/bundle.js index eb66456ce4..ae7a283fc5 100644 --- a/platform/features/timeline/bundle.js +++ b/platform/features/timeline/bundle.js @@ -91,7 +91,12 @@ define([ "name": "Export Timeline as CSV", "category": "contextual", "implementation": ExportTimelineAsCSVAction, - "depends": ["exportService", "notificationService"] + "depends": [ + "$log", + "exportService", + "notificationService", + "resources[]" + ] } ], "constants": [ diff --git a/platform/features/timeline/res/sass/_activities.scss b/platform/features/timeline/res/sass/_activities.scss index 5e729432f7..cfe69d6d01 100644 --- a/platform/features/timeline/res/sass/_activities.scss +++ b/platform/features/timeline/res/sass/_activities.scss @@ -1,16 +1,22 @@ .l-timeline-gantt { + min-width: 2px; + overflow: hidden; position: absolute; top: $timelineSwimlaneGanttVM; bottom: $timelineSwimlaneGanttVM; .bar { @include ellipsize(); height: $activityBarH; - line-height: $activityBarH + 2; + line-height: $activityBarH; padding: 0 $interiorMargin; span { - display: inline; + $iconW: 20px; + @include absPosDefault(); + display: block; &.s-activity-type { + right: auto; width: $iconW; + text-align: center; &.timeline { &:before { content:"S"; @@ -23,7 +29,9 @@ } } &.s-title { - text-shadow: rgba(black, 0.1) 0 1px 2px; + overflow: hidden; + text-overflow: ellipsis; + left: $iconW; } &.duration { left: auto; @@ -52,6 +60,10 @@ } } } + &.sm .bar span { + // Hide icon and label if width is too small + display: none; + } } .edit-mode .s-timeline-gantt, @@ -59,7 +71,7 @@ .handle { cursor: col-resize; &.mid { - cursor: move; + cursor: ew-resize; } } } \ No newline at end of file diff --git a/platform/features/timeline/res/sass/_timeline-thematic.scss b/platform/features/timeline/res/sass/_timeline-thematic.scss index 967a07462f..d4ab58b6e7 100644 --- a/platform/features/timeline/res/sass/_timeline-thematic.scss +++ b/platform/features/timeline/res/sass/_timeline-thematic.scss @@ -32,20 +32,10 @@ } .s-timeline-gantt { - $br: $controlCr; .bar { color: $colorGanttBarFg; @include activityBg($colorGanttBarBg); - border-radius: $br; box-shadow: $shdwGanttBar; - &.expanded { - @include border-top-radius($br); - @include border-bottom-radius(0); - } - &.leaf { - @include border-top-radius(0); - @include border-bottom-radius($br); - } .s-toggle { color: $colorGanttToggle; } diff --git a/platform/features/timeline/res/sass/_timelines.scss b/platform/features/timeline/res/sass/_timelines.scss index 251008a64c..006fccfbd9 100644 --- a/platform/features/timeline/res/sass/_timelines.scss +++ b/platform/features/timeline/res/sass/_timelines.scss @@ -52,6 +52,9 @@ // Tree area with item title right: auto; // Set this to auto and uncomment width below when additional tabular columns are added width: $timelineTabularTitleW; + .l-swimlanes-holder { + bottom: $scrollbarTrackSize; + } } &.l-tabular-r { // Start, end, duration, activity modes columns @@ -67,6 +70,7 @@ &.l-timeline-gantt { .l-swimlanes-holder { @include scrollV(scroll); + bottom: $scrollbarTrackSize; } } &.l-timeline-resource-legend { diff --git a/platform/features/timeline/res/templates/activity-gantt.html b/platform/features/timeline/res/templates/activity-gantt.html index 69d1d8984a..1615431e91 100644 --- a/platform/features/timeline/res/templates/activity-gantt.html +++ b/platform/features/timeline/res/templates/activity-gantt.html @@ -20,6 +20,7 @@ at runtime from the About dialog for additional information. -->
this.index ? + this.idMap[composition[this.index]] : ""; }; return CompositionColumn; diff --git a/platform/features/timeline/src/actions/ExportTimelineAsCSVAction.js b/platform/features/timeline/src/actions/ExportTimelineAsCSVAction.js index f71422358c..5b0e007e23 100644 --- a/platform/features/timeline/src/actions/ExportTimelineAsCSVAction.js +++ b/platform/features/timeline/src/actions/ExportTimelineAsCSVAction.js @@ -27,14 +27,23 @@ define(["./ExportTimelineAsCSVTask"], function (ExportTimelineAsCSVTask) { * * @param exportService the service used to perform the CSV export * @param notificationService the service used to show notifications + * @param {Array} resources an array of `resources` extensions * @param context the Action's context * @implements {Action} * @constructor * @memberof {platform/features/timeline} */ - function ExportTimelineAsCSVAction(exportService, notificationService, context) { + function ExportTimelineAsCSVAction( + $log, + exportService, + notificationService, + resources, + context + ) { + this.$log = $log; this.task = new ExportTimelineAsCSVTask( exportService, + resources, context.domainObject ); this.notificationService = notificationService; @@ -45,13 +54,15 @@ define(["./ExportTimelineAsCSVTask"], function (ExportTimelineAsCSVTask) { notification = notificationService.notify({ title: "Exporting CSV", unknownProgress: true - }); + }), + $log = this.$log; return this.task.run() .then(function () { notification.dismiss(); }) - .catch(function () { + .catch(function (err) { + $log.warn(err); notification.dismiss(); notificationService.error("Error exporting CSV"); }); diff --git a/platform/features/timeline/src/actions/ExportTimelineAsCSVTask.js b/platform/features/timeline/src/actions/ExportTimelineAsCSVTask.js index b8d796b3c4..d026edff3a 100644 --- a/platform/features/timeline/src/actions/ExportTimelineAsCSVTask.js +++ b/platform/features/timeline/src/actions/ExportTimelineAsCSVTask.js @@ -35,11 +35,13 @@ define([ * @constructor * @memberof {platform/features/timeline} * @param exportService the service used to export as CSV + * @param resources the `resources` extension category * @param {DomainObject} domainObject the timeline being exported */ - function ExportTimelineAsCSVTask(exportService, domainObject) { + function ExportTimelineAsCSVTask(exportService, resources, domainObject) { this.domainObject = domainObject; this.exportService = exportService; + this.resources = resources; } /** @@ -50,9 +52,10 @@ define([ */ ExportTimelineAsCSVTask.prototype.run = function () { var exportService = this.exportService; + var resources = this.resources; function doExport(objects) { - var exporter = new TimelineColumnizer(objects), + var exporter = new TimelineColumnizer(objects, resources), options = { headers: exporter.headers() }; return exporter.rows().then(function (rows) { return exportService.exportCSV(rows, options); diff --git a/platform/features/timeline/src/actions/IdColumn.js b/platform/features/timeline/src/actions/IdColumn.js index 38c8b9264e..9148ef6a8b 100644 --- a/platform/features/timeline/src/actions/IdColumn.js +++ b/platform/features/timeline/src/actions/IdColumn.js @@ -23,19 +23,23 @@ define([], function () { /** - * A column showing domain object identifiers. + * A column showing identifying domain objects. * @constructor + * @param idMap an object containing key value pairs, where keys + * are domain object identifiers and values are whatever + * should appear in CSV output in their place * @implements {platform/features/timeline.TimelineCSVColumn} */ - function IdColumn() { + function IdColumn(idMap) { + this.idMap = idMap; } IdColumn.prototype.name = function () { - return "Identifier"; + return "Index"; }; IdColumn.prototype.value = function (domainObject) { - return domainObject.getId(); + return this.idMap[domainObject.getId()]; }; return IdColumn; diff --git a/platform/features/timeline/src/actions/ModeColumn.js b/platform/features/timeline/src/actions/ModeColumn.js index fe2063566d..05eec7a30a 100644 --- a/platform/features/timeline/src/actions/ModeColumn.js +++ b/platform/features/timeline/src/actions/ModeColumn.js @@ -27,10 +27,14 @@ define([], function () { * @constructor * @param {number} index the zero-based index of the composition * element associated with this column + * @param idMap an object containing key value pairs, where keys + * are domain object identifiers and values are whatever + * should appear in CSV output in their place * @implements {platform/features/timeline.TimelineCSVColumn} */ - function ModeColumn(index) { + function ModeColumn(index, idMap) { this.index = index; + this.idMap = idMap; } ModeColumn.prototype.name = function () { @@ -39,8 +43,9 @@ define([], function () { ModeColumn.prototype.value = function (domainObject) { var model = domainObject.getModel(), - composition = (model.relationships || {}).modes || []; - return (composition[this.index]) || ""; + modes = (model.relationships || {}).modes || []; + return modes.length > this.index ? + this.idMap[modes[this.index]] : ""; }; return ModeColumn; diff --git a/platform/features/timeline/src/actions/TimelineColumnizer.js b/platform/features/timeline/src/actions/TimelineColumnizer.js index f24fa20eee..92e54b1860 100644 --- a/platform/features/timeline/src/actions/TimelineColumnizer.js +++ b/platform/features/timeline/src/actions/TimelineColumnizer.js @@ -25,13 +25,15 @@ define([ "./ModeColumn", "./CompositionColumn", "./MetadataColumn", - "./TimespanColumn" + "./TimespanColumn", + "./UtilizationColumn" ], function ( IdColumn, ModeColumn, CompositionColumn, MetadataColumn, - TimespanColumn + TimespanColumn, + UtilizationColumn ) { /** @@ -63,15 +65,17 @@ define([ * * @param {DomainObject[]} domainObjects the objects to include * in the exported data + * @param {Array} resources an array of `resources` extensions * @constructor * @memberof {platform/features/timeline} */ - function TimelineColumnizer(domainObjects) { + function TimelineColumnizer(domainObjects, resources) { var maxComposition = 0, maxRelationships = 0, columnNames = {}, columns = [], foundTimespan = false, + idMap, i; function addMetadataProperty(property) { @@ -82,7 +86,12 @@ define([ } } - columns.push(new IdColumn()); + idMap = domainObjects.reduce(function (map, domainObject, index) { + map[domainObject.getId()] = index + 1; + return map; + }, {}); + + columns.push(new IdColumn(idMap)); domainObjects.forEach(function (domainObject) { var model = domainObject.getModel(), @@ -113,12 +122,16 @@ define([ columns.push(new TimespanColumn(false)); } + resources.forEach(function (resource) { + columns.push(new UtilizationColumn(resource)); + }); + for (i = 0; i < maxComposition; i += 1) { - columns.push(new CompositionColumn(i)); + columns.push(new CompositionColumn(i, idMap)); } for (i = 0; i < maxRelationships; i += 1) { - columns.push(new ModeColumn(i)); + columns.push(new ModeColumn(i, idMap)); } this.domainObjects = domainObjects; diff --git a/platform/features/timeline/src/actions/UtilizationColumn.js b/platform/features/timeline/src/actions/UtilizationColumn.js new file mode 100644 index 0000000000..7a92ce668e --- /dev/null +++ b/platform/features/timeline/src/actions/UtilizationColumn.js @@ -0,0 +1,72 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2009-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define([], function () { + /** + * A column showing utilization costs associated with activities. + * @constructor + * @param {string} key the key for the particular cost + * @implements {platform/features/timeline.TimelineCSVColumn} + */ + function UtilizationColumn(resource) { + this.resource = resource; + } + + UtilizationColumn.prototype.name = function () { + var units = { + "Kbps": "Kb", + "watts": "watt-seconds" + }[this.resource.units] || "unknown units"; + + return this.resource.name + " (" + units + ")"; + }; + + UtilizationColumn.prototype.value = function (domainObject) { + var resource = this.resource; + + function getCost(utilization) { + var seconds = (utilization.end - utilization.start) / 1000; + return seconds * utilization.value; + } + + function getUtilizationValue(utilizations) { + utilizations = utilizations.filter(function (utilization) { + return utilization.key === resource.key; + }); + + if (utilizations.length === 0) { + return ""; + } + + return utilizations.map(getCost).reduce(function (a, b) { + return a + b; + }, 0); + } + + return domainObject.hasCapability('utilization') ? + domainObject.getCapability('utilization').internal() + .then(getUtilizationValue) : + ""; + }; + + return UtilizationColumn; +}); diff --git a/platform/features/timeline/src/capabilities/UtilizationCapability.js b/platform/features/timeline/src/capabilities/UtilizationCapability.js index 2744976615..55976f3ab5 100644 --- a/platform/features/timeline/src/capabilities/UtilizationCapability.js +++ b/platform/features/timeline/src/capabilities/UtilizationCapability.js @@ -193,6 +193,13 @@ define( * @returns {Promise.} a promise for resource identifiers */ resources: promiseResourceKeys, + /** + * Get the resource utilization associated with this object + * directly, not including any resource utilization associated + * with contained objects. + * @returns {Promise.} + */ + internal: promiseInternalUtilization, /** * Get the resource utilization associated with this * object. Results are not sorted. This requires looking diff --git a/platform/features/timeline/test/actions/CompositionColumnSpec.js b/platform/features/timeline/test/actions/CompositionColumnSpec.js index 8cf566a080..df52d08db5 100644 --- a/platform/features/timeline/test/actions/CompositionColumnSpec.js +++ b/platform/features/timeline/test/actions/CompositionColumnSpec.js @@ -23,13 +23,20 @@ define( ['../../src/actions/CompositionColumn'], function (CompositionColumn) { + var TEST_IDS = ['a', 'b', 'c', 'd', 'e', 'f']; + describe("CompositionColumn", function () { var testIndex, + testIdMap, column; beforeEach(function () { testIndex = 3; - column = new CompositionColumn(testIndex); + testIdMap = TEST_IDS.reduce(function (map, id, index) { + map[id] = index; + return map; + }, {}); + column = new CompositionColumn(testIndex, testIdMap); }); it("includes a one-based index in its name", function () { @@ -46,15 +53,13 @@ define( 'domainObject', ['getId', 'getModel', 'getCapability'] ); - testModel = { - composition: ['a', 'b', 'c', 'd', 'e', 'f'] - }; + testModel = { composition: TEST_IDS }; mockDomainObject.getModel.andReturn(testModel); }); - it("returns a corresponding identifier", function () { + it("returns a corresponding value from the map", function () { expect(column.value(mockDomainObject)) - .toEqual(testModel.composition[testIndex]); + .toEqual(testIdMap[testModel.composition[testIndex]]); }); it("returns nothing when composition is exceeded", function () { diff --git a/platform/features/timeline/test/actions/ExportTimelineAsCSVActionSpec.js b/platform/features/timeline/test/actions/ExportTimelineAsCSVActionSpec.js index e0f09c3ae6..e31f25b074 100644 --- a/platform/features/timeline/test/actions/ExportTimelineAsCSVActionSpec.js +++ b/platform/features/timeline/test/actions/ExportTimelineAsCSVActionSpec.js @@ -24,7 +24,8 @@ define( ['../../src/actions/ExportTimelineAsCSVAction'], function (ExportTimelineAsCSVAction) { describe("ExportTimelineAsCSVAction", function () { - var mockExportService, + var mockLog, + mockExportService, mockNotificationService, mockNotification, mockDomainObject, @@ -39,6 +40,13 @@ define( ['getId', 'getModel', 'getCapability', 'hasCapability'] ); mockType = jasmine.createSpyObj('type', ['instanceOf']); + + mockLog = jasmine.createSpyObj('$log', [ + 'warn', + 'error', + 'info', + 'debug' + ]); mockExportService = jasmine.createSpyObj( 'exportService', ['exportCSV'] @@ -63,8 +71,10 @@ define( testContext = { domainObject: mockDomainObject }; action = new ExportTimelineAsCSVAction( + mockLog, mockExportService, mockNotificationService, + [], testContext ); }); @@ -129,8 +139,11 @@ define( }); describe("and an error occurs", function () { + var testError; + beforeEach(function () { - testPromise.reject(); + testError = { someProperty: "some value" }; + testPromise.reject(testError); waitsFor(function () { return mockCallback.calls.length > 0; }); @@ -145,6 +158,10 @@ define( expect(mockNotificationService.error) .toHaveBeenCalledWith(jasmine.any(String)); }); + + it("logs the root cause", function () { + expect(mockLog.warn).toHaveBeenCalledWith(testError); + }); }); }); }); diff --git a/platform/features/timeline/test/actions/ExportTimelineAsCSVTaskSpec.js b/platform/features/timeline/test/actions/ExportTimelineAsCSVTaskSpec.js index 0330e86397..4deab99801 100644 --- a/platform/features/timeline/test/actions/ExportTimelineAsCSVTaskSpec.js +++ b/platform/features/timeline/test/actions/ExportTimelineAsCSVTaskSpec.js @@ -52,6 +52,7 @@ define( task = new ExportTimelineAsCSVTask( mockExportService, + [], mockDomainObject ); }); diff --git a/platform/features/timeline/test/actions/IdColumnSpec.js b/platform/features/timeline/test/actions/IdColumnSpec.js index f44d255255..80b84680a4 100644 --- a/platform/features/timeline/test/actions/IdColumnSpec.js +++ b/platform/features/timeline/test/actions/IdColumnSpec.js @@ -24,10 +24,12 @@ define( ['../../src/actions/IdColumn'], function (IdColumn) { describe("IdColumn", function () { - var column; + var testIdMap, + column; beforeEach(function () { - column = new IdColumn(); + testIdMap = { "foo": "bar" }; + column = new IdColumn(testIdMap); }); it("has a name", function () { @@ -47,9 +49,9 @@ define( mockDomainObject.getId.andReturn(testId); }); - it("provides a domain object's identifier", function () { + it("provides a value mapped from domain object's identifier", function () { expect(column.value(mockDomainObject)) - .toEqual(testId); + .toEqual(testIdMap[testId]); }); }); diff --git a/platform/features/timeline/test/actions/ModeColumnSpec.js b/platform/features/timeline/test/actions/ModeColumnSpec.js index 446e3b1030..037aa5c34f 100644 --- a/platform/features/timeline/test/actions/ModeColumnSpec.js +++ b/platform/features/timeline/test/actions/ModeColumnSpec.js @@ -23,13 +23,20 @@ define( ['../../src/actions/ModeColumn'], function (ModeColumn) { + var TEST_IDS = ['a', 'b', 'c', 'd', 'e', 'f']; + describe("ModeColumn", function () { var testIndex, + testIdMap, column; beforeEach(function () { testIndex = 3; - column = new ModeColumn(testIndex); + testIdMap = TEST_IDS.reduce(function (map, id, index) { + map[id] = index; + return map; + }, {}); + column = new ModeColumn(testIndex, testIdMap); }); it("includes a one-based index in its name", function () { @@ -48,15 +55,15 @@ define( ); testModel = { relationships: { - modes: ['a', 'b', 'c', 'd', 'e', 'f'] + modes: TEST_IDS } }; mockDomainObject.getModel.andReturn(testModel); }); - it("returns a corresponding identifier", function () { + it("returns a corresponding value from the map", function () { expect(column.value(mockDomainObject)) - .toEqual(testModel.relationships.modes[testIndex]); + .toEqual(testIdMap[testModel.relationships.modes[testIndex]]); }); it("returns nothing when relationships are exceeded", function () { diff --git a/platform/features/timeline/test/actions/TimelineColumnizerSpec.js b/platform/features/timeline/test/actions/TimelineColumnizerSpec.js index d29bb14278..980ed1e6c3 100644 --- a/platform/features/timeline/test/actions/TimelineColumnizerSpec.js +++ b/platform/features/timeline/test/actions/TimelineColumnizerSpec.js @@ -75,7 +75,7 @@ define( return c === 'metadata' && testMetadata; }); - exporter = new TimelineColumnizer(mockDomainObjects); + exporter = new TimelineColumnizer(mockDomainObjects, []); }); describe("rows", function () { @@ -94,13 +94,6 @@ define( it("include one row per domain object", function () { expect(rows.length).toEqual(mockDomainObjects.length); }); - - it("includes identifiers for each domain object", function () { - rows.forEach(function (row, index) { - var id = mockDomainObjects[index].getId(); - expect(row.indexOf(id)).not.toEqual(-1); - }); - }); }); describe("headers", function () {