Merge remote-tracking branch 'origin/master' into table-export-934

This commit is contained in:
Victor Woeltjen
2016-06-01 10:33:53 -07:00
24 changed files with 485 additions and 61 deletions

View File

@@ -27,6 +27,7 @@ define([
"./src/MenuArrowController", "./src/MenuArrowController",
"./src/navigation/NavigationService", "./src/navigation/NavigationService",
"./src/navigation/NavigateAction", "./src/navigation/NavigateAction",
"./src/navigation/OrphanNavigationHandler",
"./src/windowing/NewTabAction", "./src/windowing/NewTabAction",
"./src/windowing/FullscreenAction", "./src/windowing/FullscreenAction",
"./src/windowing/WindowTitler", "./src/windowing/WindowTitler",
@@ -47,6 +48,7 @@ define([
MenuArrowController, MenuArrowController,
NavigationService, NavigationService,
NavigateAction, NavigateAction,
OrphanNavigationHandler,
NewTabAction, NewTabAction,
FullscreenAction, FullscreenAction,
WindowTitler, WindowTitler,
@@ -253,6 +255,14 @@ define([
"$rootScope", "$rootScope",
"$document" "$document"
] ]
},
{
"implementation": OrphanNavigationHandler,
"depends": [
"throttle",
"topic",
"navigationService"
]
} }
], ],
"licenses": [ "licenses": [

View File

@@ -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;
});

View File

@@ -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();
});
});
});
});
});
});
});

View File

@@ -253,7 +253,7 @@ define([
}, },
{ {
"category": "navigation", "category": "navigation",
"message": "There are unsaved changes.", "message": "Continuing will cause the loss of any unsaved changes.",
"implementation": EditNavigationPolicy "implementation": EditNavigationPolicy
}, },
{ {

View File

@@ -288,8 +288,9 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
.left { .left {
padding-right: $interiorMarginLg; padding-right: $interiorMarginLg;
.l-back:not(.s-status-editing) { .l-back {
margin-right: $interiorMarginLg; margin-right: $interiorMarginLg;
&.s-status-editing { display: none; }
} }
} }
} }

View File

@@ -91,7 +91,12 @@ define([
"name": "Export Timeline as CSV", "name": "Export Timeline as CSV",
"category": "contextual", "category": "contextual",
"implementation": ExportTimelineAsCSVAction, "implementation": ExportTimelineAsCSVAction,
"depends": ["exportService", "notificationService"] "depends": [
"$log",
"exportService",
"notificationService",
"resources[]"
]
} }
], ],
"constants": [ "constants": [

View File

@@ -1,16 +1,22 @@
.l-timeline-gantt { .l-timeline-gantt {
min-width: 2px;
overflow: hidden;
position: absolute; position: absolute;
top: $timelineSwimlaneGanttVM; bottom: $timelineSwimlaneGanttVM; top: $timelineSwimlaneGanttVM; bottom: $timelineSwimlaneGanttVM;
.bar { .bar {
@include ellipsize(); @include ellipsize();
height: $activityBarH; height: $activityBarH;
line-height: $activityBarH + 2; line-height: $activityBarH;
padding: 0 $interiorMargin; padding: 0 $interiorMargin;
span { span {
display: inline; $iconW: 20px;
@include absPosDefault();
display: block;
&.s-activity-type { &.s-activity-type {
right: auto; width: $iconW;
text-align: center;
&.timeline { &.timeline {
&:before { &:before {
content:"S"; content:"S";
@@ -23,7 +29,9 @@
} }
} }
&.s-title { &.s-title {
text-shadow: rgba(black, 0.1) 0 1px 2px; overflow: hidden;
text-overflow: ellipsis;
left: $iconW;
} }
&.duration { &.duration {
left: auto; 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, .edit-mode .s-timeline-gantt,
@@ -59,7 +71,7 @@
.handle { .handle {
cursor: col-resize; cursor: col-resize;
&.mid { &.mid {
cursor: move; cursor: ew-resize;
} }
} }
} }

View File

@@ -32,20 +32,10 @@
} }
.s-timeline-gantt { .s-timeline-gantt {
$br: $controlCr;
.bar { .bar {
color: $colorGanttBarFg; color: $colorGanttBarFg;
@include activityBg($colorGanttBarBg); @include activityBg($colorGanttBarBg);
border-radius: $br;
box-shadow: $shdwGanttBar; 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 { .s-toggle {
color: $colorGanttToggle; color: $colorGanttToggle;
} }

View File

@@ -52,6 +52,9 @@
// Tree area with item title // Tree area with item title
right: auto; // Set this to auto and uncomment width below when additional tabular columns are added right: auto; // Set this to auto and uncomment width below when additional tabular columns are added
width: $timelineTabularTitleW; width: $timelineTabularTitleW;
.l-swimlanes-holder {
bottom: $scrollbarTrackSize;
}
} }
&.l-tabular-r { &.l-tabular-r {
// Start, end, duration, activity modes columns // Start, end, duration, activity modes columns
@@ -67,6 +70,7 @@
&.l-timeline-gantt { &.l-timeline-gantt {
.l-swimlanes-holder { .l-swimlanes-holder {
@include scrollV(scroll); @include scrollV(scroll);
bottom: $scrollbarTrackSize;
} }
} }
&.l-timeline-resource-legend { &.l-timeline-resource-legend {

View File

@@ -20,6 +20,7 @@
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<div class="t-timeline-gantt l-timeline-gantt s-timeline-gantt" <div class="t-timeline-gantt l-timeline-gantt s-timeline-gantt"
ng-class="{ sm: gantt.width(timespan, parameters.scroll, parameters.toPixels) < 25 }"
title="{{model.name}}" title="{{model.name}}"
ng-controller="TimelineGanttController as gantt" ng-controller="TimelineGanttController as gantt"
ng-style="timespan ? { ng-style="timespan ? {

View File

@@ -27,11 +27,15 @@ define([], function () {
* in a domain object's composition. * in a domain object's composition.
* @param {number} index the zero-based index of the composition * @param {number} index the zero-based index of the composition
* element associated with this column * 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
* @constructor * @constructor
* @implements {platform/features/timeline.TimelineCSVColumn} * @implements {platform/features/timeline.TimelineCSVColumn}
*/ */
function CompositionColumn(index) { function CompositionColumn(index, idMap) {
this.index = index; this.index = index;
this.idMap = idMap;
} }
CompositionColumn.prototype.name = function () { CompositionColumn.prototype.name = function () {
@@ -41,7 +45,9 @@ define([], function () {
CompositionColumn.prototype.value = function (domainObject) { CompositionColumn.prototype.value = function (domainObject) {
var model = domainObject.getModel(), var model = domainObject.getModel(),
composition = model.composition || []; composition = model.composition || [];
return (composition[this.index]) || "";
return composition.length > this.index ?
this.idMap[composition[this.index]] : "";
}; };
return CompositionColumn; return CompositionColumn;

View File

@@ -27,14 +27,23 @@ define(["./ExportTimelineAsCSVTask"], function (ExportTimelineAsCSVTask) {
* *
* @param exportService the service used to perform the CSV export * @param exportService the service used to perform the CSV export
* @param notificationService the service used to show notifications * @param notificationService the service used to show notifications
* @param {Array} resources an array of `resources` extensions
* @param context the Action's context * @param context the Action's context
* @implements {Action} * @implements {Action}
* @constructor * @constructor
* @memberof {platform/features/timeline} * @memberof {platform/features/timeline}
*/ */
function ExportTimelineAsCSVAction(exportService, notificationService, context) { function ExportTimelineAsCSVAction(
$log,
exportService,
notificationService,
resources,
context
) {
this.$log = $log;
this.task = new ExportTimelineAsCSVTask( this.task = new ExportTimelineAsCSVTask(
exportService, exportService,
resources,
context.domainObject context.domainObject
); );
this.notificationService = notificationService; this.notificationService = notificationService;
@@ -45,13 +54,15 @@ define(["./ExportTimelineAsCSVTask"], function (ExportTimelineAsCSVTask) {
notification = notificationService.notify({ notification = notificationService.notify({
title: "Exporting CSV", title: "Exporting CSV",
unknownProgress: true unknownProgress: true
}); }),
$log = this.$log;
return this.task.run() return this.task.run()
.then(function () { .then(function () {
notification.dismiss(); notification.dismiss();
}) })
.catch(function () { .catch(function (err) {
$log.warn(err);
notification.dismiss(); notification.dismiss();
notificationService.error("Error exporting CSV"); notificationService.error("Error exporting CSV");
}); });

View File

@@ -35,11 +35,13 @@ define([
* @constructor * @constructor
* @memberof {platform/features/timeline} * @memberof {platform/features/timeline}
* @param exportService the service used to export as CSV * @param exportService the service used to export as CSV
* @param resources the `resources` extension category
* @param {DomainObject} domainObject the timeline being exported * @param {DomainObject} domainObject the timeline being exported
*/ */
function ExportTimelineAsCSVTask(exportService, domainObject) { function ExportTimelineAsCSVTask(exportService, resources, domainObject) {
this.domainObject = domainObject; this.domainObject = domainObject;
this.exportService = exportService; this.exportService = exportService;
this.resources = resources;
} }
/** /**
@@ -50,9 +52,10 @@ define([
*/ */
ExportTimelineAsCSVTask.prototype.run = function () { ExportTimelineAsCSVTask.prototype.run = function () {
var exportService = this.exportService; var exportService = this.exportService;
var resources = this.resources;
function doExport(objects) { function doExport(objects) {
var exporter = new TimelineColumnizer(objects), var exporter = new TimelineColumnizer(objects, resources),
options = { headers: exporter.headers() }; options = { headers: exporter.headers() };
return exporter.rows().then(function (rows) { return exporter.rows().then(function (rows) {
return exportService.exportCSV(rows, options); return exportService.exportCSV(rows, options);

View File

@@ -23,19 +23,23 @@
define([], function () { define([], function () {
/** /**
* A column showing domain object identifiers. * A column showing identifying domain objects.
* @constructor * @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} * @implements {platform/features/timeline.TimelineCSVColumn}
*/ */
function IdColumn() { function IdColumn(idMap) {
this.idMap = idMap;
} }
IdColumn.prototype.name = function () { IdColumn.prototype.name = function () {
return "Identifier"; return "Index";
}; };
IdColumn.prototype.value = function (domainObject) { IdColumn.prototype.value = function (domainObject) {
return domainObject.getId(); return this.idMap[domainObject.getId()];
}; };
return IdColumn; return IdColumn;

View File

@@ -27,10 +27,14 @@ define([], function () {
* @constructor * @constructor
* @param {number} index the zero-based index of the composition * @param {number} index the zero-based index of the composition
* element associated with this column * 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} * @implements {platform/features/timeline.TimelineCSVColumn}
*/ */
function ModeColumn(index) { function ModeColumn(index, idMap) {
this.index = index; this.index = index;
this.idMap = idMap;
} }
ModeColumn.prototype.name = function () { ModeColumn.prototype.name = function () {
@@ -39,8 +43,9 @@ define([], function () {
ModeColumn.prototype.value = function (domainObject) { ModeColumn.prototype.value = function (domainObject) {
var model = domainObject.getModel(), var model = domainObject.getModel(),
composition = (model.relationships || {}).modes || []; modes = (model.relationships || {}).modes || [];
return (composition[this.index]) || ""; return modes.length > this.index ?
this.idMap[modes[this.index]] : "";
}; };
return ModeColumn; return ModeColumn;

View File

@@ -25,13 +25,15 @@ define([
"./ModeColumn", "./ModeColumn",
"./CompositionColumn", "./CompositionColumn",
"./MetadataColumn", "./MetadataColumn",
"./TimespanColumn" "./TimespanColumn",
"./UtilizationColumn"
], function ( ], function (
IdColumn, IdColumn,
ModeColumn, ModeColumn,
CompositionColumn, CompositionColumn,
MetadataColumn, MetadataColumn,
TimespanColumn TimespanColumn,
UtilizationColumn
) { ) {
/** /**
@@ -63,15 +65,17 @@ define([
* *
* @param {DomainObject[]} domainObjects the objects to include * @param {DomainObject[]} domainObjects the objects to include
* in the exported data * in the exported data
* @param {Array} resources an array of `resources` extensions
* @constructor * @constructor
* @memberof {platform/features/timeline} * @memberof {platform/features/timeline}
*/ */
function TimelineColumnizer(domainObjects) { function TimelineColumnizer(domainObjects, resources) {
var maxComposition = 0, var maxComposition = 0,
maxRelationships = 0, maxRelationships = 0,
columnNames = {}, columnNames = {},
columns = [], columns = [],
foundTimespan = false, foundTimespan = false,
idMap,
i; i;
function addMetadataProperty(property) { 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) { domainObjects.forEach(function (domainObject) {
var model = domainObject.getModel(), var model = domainObject.getModel(),
@@ -113,12 +122,16 @@ define([
columns.push(new TimespanColumn(false)); columns.push(new TimespanColumn(false));
} }
resources.forEach(function (resource) {
columns.push(new UtilizationColumn(resource));
});
for (i = 0; i < maxComposition; i += 1) { 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) { for (i = 0; i < maxRelationships; i += 1) {
columns.push(new ModeColumn(i)); columns.push(new ModeColumn(i, idMap));
} }
this.domainObjects = domainObjects; this.domainObjects = domainObjects;

View File

@@ -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;
});

View File

@@ -193,6 +193,13 @@ define(
* @returns {Promise.<string[]>} a promise for resource identifiers * @returns {Promise.<string[]>} a promise for resource identifiers
*/ */
resources: promiseResourceKeys, resources: promiseResourceKeys,
/**
* Get the resource utilization associated with this object
* directly, not including any resource utilization associated
* with contained objects.
* @returns {Promise.<Array>}
*/
internal: promiseInternalUtilization,
/** /**
* Get the resource utilization associated with this * Get the resource utilization associated with this
* object. Results are not sorted. This requires looking * object. Results are not sorted. This requires looking

View File

@@ -23,13 +23,20 @@
define( define(
['../../src/actions/CompositionColumn'], ['../../src/actions/CompositionColumn'],
function (CompositionColumn) { function (CompositionColumn) {
var TEST_IDS = ['a', 'b', 'c', 'd', 'e', 'f'];
describe("CompositionColumn", function () { describe("CompositionColumn", function () {
var testIndex, var testIndex,
testIdMap,
column; column;
beforeEach(function () { beforeEach(function () {
testIndex = 3; 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 () { it("includes a one-based index in its name", function () {
@@ -46,15 +53,13 @@ define(
'domainObject', 'domainObject',
['getId', 'getModel', 'getCapability'] ['getId', 'getModel', 'getCapability']
); );
testModel = { testModel = { composition: TEST_IDS };
composition: ['a', 'b', 'c', 'd', 'e', 'f']
};
mockDomainObject.getModel.andReturn(testModel); mockDomainObject.getModel.andReturn(testModel);
}); });
it("returns a corresponding identifier", function () { it("returns a corresponding value from the map", function () {
expect(column.value(mockDomainObject)) expect(column.value(mockDomainObject))
.toEqual(testModel.composition[testIndex]); .toEqual(testIdMap[testModel.composition[testIndex]]);
}); });
it("returns nothing when composition is exceeded", function () { it("returns nothing when composition is exceeded", function () {

View File

@@ -24,7 +24,8 @@ define(
['../../src/actions/ExportTimelineAsCSVAction'], ['../../src/actions/ExportTimelineAsCSVAction'],
function (ExportTimelineAsCSVAction) { function (ExportTimelineAsCSVAction) {
describe("ExportTimelineAsCSVAction", function () { describe("ExportTimelineAsCSVAction", function () {
var mockExportService, var mockLog,
mockExportService,
mockNotificationService, mockNotificationService,
mockNotification, mockNotification,
mockDomainObject, mockDomainObject,
@@ -39,6 +40,13 @@ define(
['getId', 'getModel', 'getCapability', 'hasCapability'] ['getId', 'getModel', 'getCapability', 'hasCapability']
); );
mockType = jasmine.createSpyObj('type', ['instanceOf']); mockType = jasmine.createSpyObj('type', ['instanceOf']);
mockLog = jasmine.createSpyObj('$log', [
'warn',
'error',
'info',
'debug'
]);
mockExportService = jasmine.createSpyObj( mockExportService = jasmine.createSpyObj(
'exportService', 'exportService',
['exportCSV'] ['exportCSV']
@@ -63,8 +71,10 @@ define(
testContext = { domainObject: mockDomainObject }; testContext = { domainObject: mockDomainObject };
action = new ExportTimelineAsCSVAction( action = new ExportTimelineAsCSVAction(
mockLog,
mockExportService, mockExportService,
mockNotificationService, mockNotificationService,
[],
testContext testContext
); );
}); });
@@ -129,8 +139,11 @@ define(
}); });
describe("and an error occurs", function () { describe("and an error occurs", function () {
var testError;
beforeEach(function () { beforeEach(function () {
testPromise.reject(); testError = { someProperty: "some value" };
testPromise.reject(testError);
waitsFor(function () { waitsFor(function () {
return mockCallback.calls.length > 0; return mockCallback.calls.length > 0;
}); });
@@ -145,6 +158,10 @@ define(
expect(mockNotificationService.error) expect(mockNotificationService.error)
.toHaveBeenCalledWith(jasmine.any(String)); .toHaveBeenCalledWith(jasmine.any(String));
}); });
it("logs the root cause", function () {
expect(mockLog.warn).toHaveBeenCalledWith(testError);
});
}); });
}); });
}); });

View File

@@ -52,6 +52,7 @@ define(
task = new ExportTimelineAsCSVTask( task = new ExportTimelineAsCSVTask(
mockExportService, mockExportService,
[],
mockDomainObject mockDomainObject
); );
}); });

View File

@@ -24,10 +24,12 @@ define(
['../../src/actions/IdColumn'], ['../../src/actions/IdColumn'],
function (IdColumn) { function (IdColumn) {
describe("IdColumn", function () { describe("IdColumn", function () {
var column; var testIdMap,
column;
beforeEach(function () { beforeEach(function () {
column = new IdColumn(); testIdMap = { "foo": "bar" };
column = new IdColumn(testIdMap);
}); });
it("has a name", function () { it("has a name", function () {
@@ -47,9 +49,9 @@ define(
mockDomainObject.getId.andReturn(testId); 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)) expect(column.value(mockDomainObject))
.toEqual(testId); .toEqual(testIdMap[testId]);
}); });
}); });

View File

@@ -23,13 +23,20 @@
define( define(
['../../src/actions/ModeColumn'], ['../../src/actions/ModeColumn'],
function (ModeColumn) { function (ModeColumn) {
var TEST_IDS = ['a', 'b', 'c', 'd', 'e', 'f'];
describe("ModeColumn", function () { describe("ModeColumn", function () {
var testIndex, var testIndex,
testIdMap,
column; column;
beforeEach(function () { beforeEach(function () {
testIndex = 3; 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 () { it("includes a one-based index in its name", function () {
@@ -48,15 +55,15 @@ define(
); );
testModel = { testModel = {
relationships: { relationships: {
modes: ['a', 'b', 'c', 'd', 'e', 'f'] modes: TEST_IDS
} }
}; };
mockDomainObject.getModel.andReturn(testModel); mockDomainObject.getModel.andReturn(testModel);
}); });
it("returns a corresponding identifier", function () { it("returns a corresponding value from the map", function () {
expect(column.value(mockDomainObject)) expect(column.value(mockDomainObject))
.toEqual(testModel.relationships.modes[testIndex]); .toEqual(testIdMap[testModel.relationships.modes[testIndex]]);
}); });
it("returns nothing when relationships are exceeded", function () { it("returns nothing when relationships are exceeded", function () {

View File

@@ -75,7 +75,7 @@ define(
return c === 'metadata' && testMetadata; return c === 'metadata' && testMetadata;
}); });
exporter = new TimelineColumnizer(mockDomainObjects); exporter = new TimelineColumnizer(mockDomainObjects, []);
}); });
describe("rows", function () { describe("rows", function () {
@@ -94,13 +94,6 @@ define(
it("include one row per domain object", function () { it("include one row per domain object", function () {
expect(rows.length).toEqual(mockDomainObjects.length); 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 () { describe("headers", function () {