[Features] Added Autoflow Tabular to open source features. Fixes #1469
This commit is contained in:
178
platform/features/autoflow/test/AutoflowTableLinkerSpec.js
Executable file
178
platform/features/autoflow/test/AutoflowTableLinkerSpec.js
Executable file
@@ -0,0 +1,178 @@
|
||||
|
||||
define(
|
||||
["../src/AutoflowTableLinker"],
|
||||
function (AutoflowTableLinker) {
|
||||
|
||||
describe("The mct-autoflow-table linker", function () {
|
||||
var cachedAngular,
|
||||
mockAngular,
|
||||
mockScope,
|
||||
mockElement,
|
||||
mockElements,
|
||||
linker;
|
||||
|
||||
// Utility function to generate more mock elements
|
||||
function createMockElement(html) {
|
||||
var mockEl = jasmine.createSpyObj(
|
||||
"element-" + html,
|
||||
[
|
||||
"append",
|
||||
"addClass",
|
||||
"removeClass",
|
||||
"text",
|
||||
"attr",
|
||||
"html",
|
||||
"css",
|
||||
"find"
|
||||
]
|
||||
);
|
||||
mockEl.testHtml = html;
|
||||
mockEl.append.andReturn(mockEl);
|
||||
mockElements.push(mockEl);
|
||||
return mockEl;
|
||||
}
|
||||
|
||||
function createMockDomainObject(id) {
|
||||
var mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject-" + id,
|
||||
["getId", "getModel"]
|
||||
);
|
||||
mockDomainObject.getId.andReturn(id);
|
||||
mockDomainObject.getModel.andReturn({name: id.toUpperCase()});
|
||||
return mockDomainObject;
|
||||
}
|
||||
|
||||
function fireWatch(watchExpression, value) {
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === watchExpression) {
|
||||
call.args[1](value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// AutoflowTableLinker accesses Angular in the global
|
||||
// scope, since it is not injectable; we simulate that
|
||||
// here by adding/removing it to/from the window object.
|
||||
beforeEach(function () {
|
||||
mockElements = [];
|
||||
|
||||
mockAngular = jasmine.createSpyObj("angular", ["element"]);
|
||||
mockScope = jasmine.createSpyObj("scope", ["$watch"]);
|
||||
mockElement = createMockElement('<div>');
|
||||
|
||||
mockAngular.element.andCallFake(createMockElement);
|
||||
|
||||
if (window.angular !== undefined) {
|
||||
cachedAngular = window.angular;
|
||||
}
|
||||
window.angular = mockAngular;
|
||||
|
||||
linker = new AutoflowTableLinker(mockScope, mockElement);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if (cachedAngular !== undefined) {
|
||||
window.angular = cachedAngular;
|
||||
} else {
|
||||
delete window.angular;
|
||||
}
|
||||
});
|
||||
|
||||
it("watches for changes in inputs", function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"objects",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"rows",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"counter",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("changes structure when domain objects change", function () {
|
||||
// Set up scope
|
||||
mockScope.rows = 4;
|
||||
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
.map(createMockDomainObject);
|
||||
|
||||
// Fire an update to the set of objects
|
||||
fireWatch("objects");
|
||||
|
||||
// Should have rebuilt with two columns of
|
||||
// four and two rows each; first, by clearing...
|
||||
expect(mockElement.html).toHaveBeenCalledWith("");
|
||||
|
||||
// Should have appended two columns...
|
||||
expect(mockElement.append.calls.length).toEqual(2);
|
||||
|
||||
// ...which should have received two and four rows each
|
||||
expect(mockElement.append.calls[0].args[0].append.calls.length)
|
||||
.toEqual(4);
|
||||
expect(mockElement.append.calls[1].args[0].append.calls.length)
|
||||
.toEqual(2);
|
||||
});
|
||||
|
||||
it("updates values", function () {
|
||||
var mockSpans;
|
||||
|
||||
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
.map(createMockDomainObject);
|
||||
mockScope.values = { a: 0 };
|
||||
|
||||
// Fire an update to the set of values
|
||||
fireWatch("objects");
|
||||
fireWatch("updated");
|
||||
|
||||
// Get all created spans
|
||||
mockSpans = mockElements.filter(function (mockElem) {
|
||||
return mockElem.testHtml === '<span>';
|
||||
});
|
||||
|
||||
// First span should be a, should have gotten this value.
|
||||
// This test detects, in particular, WTD-749
|
||||
expect(mockSpans[0].text).toHaveBeenCalledWith('A');
|
||||
expect(mockSpans[1].text).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it("listens for changes in column width", function () {
|
||||
var mockUL = createMockElement("<ul>");
|
||||
mockElement.find.andReturn(mockUL);
|
||||
mockScope.columnWidth = 200;
|
||||
fireWatch("columnWidth", mockScope.columnWidth);
|
||||
expect(mockUL.css).toHaveBeenCalledWith("width", "200px");
|
||||
});
|
||||
|
||||
it("updates CSS classes", function () {
|
||||
var mockSpans;
|
||||
|
||||
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
.map(createMockDomainObject);
|
||||
mockScope.values = { a: "a value to find" };
|
||||
mockScope.classes = { a: 'class-a' };
|
||||
|
||||
// Fire an update to the set of values
|
||||
fireWatch("objects");
|
||||
fireWatch("updated");
|
||||
|
||||
// Figure out which span holds the relevant value...
|
||||
mockSpans = mockElements.filter(function (mockElem) {
|
||||
return mockElem.testHtml === '<span>';
|
||||
}).filter(function (mockSpan) {
|
||||
var attrCalls = mockSpan.attr.calls;
|
||||
return attrCalls.some(function (call) {
|
||||
return call.args[0] === 'title' &&
|
||||
call.args[1] === mockScope.values.a;
|
||||
});
|
||||
});
|
||||
|
||||
// ...and make sure it also has had its class applied
|
||||
expect(mockSpans[0].addClass)
|
||||
.toHaveBeenCalledWith(mockScope.classes.a);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
341
platform/features/autoflow/test/AutoflowTabularControllerSpec.js
Executable file
341
platform/features/autoflow/test/AutoflowTabularControllerSpec.js
Executable file
@@ -0,0 +1,341 @@
|
||||
|
||||
define(
|
||||
["../src/AutoflowTabularController"],
|
||||
function (AutoflowTabularController) {
|
||||
|
||||
describe("The autoflow tabular controller", function () {
|
||||
var mockScope,
|
||||
mockTimeout,
|
||||
mockSubscriber,
|
||||
mockDomainObject,
|
||||
mockSubscription,
|
||||
controller;
|
||||
|
||||
// Fire watches that are registered as functions.
|
||||
function fireFnWatches() {
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
if (typeof call.args[0] === 'function') {
|
||||
call.args[1](call.args[0]());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
["$on", "$watch"]
|
||||
);
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockSubscriber = jasmine.createSpyObj(
|
||||
"telemetrySubscriber",
|
||||
["subscribe"]
|
||||
);
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
["getId", "getModel", "getCapability"]
|
||||
);
|
||||
mockSubscription = jasmine.createSpyObj(
|
||||
"subscription",
|
||||
[
|
||||
"unsubscribe",
|
||||
"getTelemetryObjects",
|
||||
"getDomainValue",
|
||||
"getRangeValue"
|
||||
]
|
||||
);
|
||||
|
||||
mockSubscriber.subscribe.andReturn(mockSubscription);
|
||||
mockDomainObject.getModel.andReturn({name: "something"});
|
||||
|
||||
controller = new AutoflowTabularController(
|
||||
mockScope,
|
||||
mockTimeout,
|
||||
mockSubscriber
|
||||
);
|
||||
});
|
||||
|
||||
it("listens for the represented domain object", function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"domainObject",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("provides a getter-setter function for filtering", function () {
|
||||
expect(controller.filter()).toEqual("");
|
||||
controller.filter("something");
|
||||
expect(controller.filter()).toEqual("something");
|
||||
});
|
||||
|
||||
it("tracks bounds and adjust number of rows accordingly", function () {
|
||||
// Rows are 15px high, and need room for an 10px slider
|
||||
controller.setBounds({ width: 700, height: 120 });
|
||||
expect(controller.getRows()).toEqual(6); // 110 usable height / 16px
|
||||
controller.setBounds({ width: 700, height: 240 });
|
||||
expect(controller.getRows()).toEqual(14); // 230 usable height / 16px
|
||||
});
|
||||
|
||||
it("subscribes to a represented object's telemetry", function () {
|
||||
// Set up subscription, scope
|
||||
mockSubscription.getTelemetryObjects
|
||||
.andReturn([mockDomainObject]);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
// Invoke the watcher with represented domain object
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
// Should have subscribed to it
|
||||
expect(mockSubscriber.subscribe).toHaveBeenCalledWith(
|
||||
mockDomainObject,
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
// Should report objects as reported from subscription
|
||||
expect(controller.getTelemetryObjects())
|
||||
.toEqual([mockDomainObject]);
|
||||
});
|
||||
|
||||
it("releases subscriptions on destroy", function () {
|
||||
// Set up subscription...
|
||||
mockSubscription.getTelemetryObjects
|
||||
.andReturn([mockDomainObject]);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
// Verify precondition
|
||||
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
||||
|
||||
// Make sure we're listening for $destroy
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
"$destroy",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
// Fire a destroy event
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
|
||||
// Should have unsubscribed
|
||||
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("presents latest values and latest update state", function () {
|
||||
// Make sure values are available
|
||||
mockSubscription.getDomainValue.andReturn(402654321123);
|
||||
mockSubscription.getRangeValue.andReturn(789);
|
||||
mockDomainObject.getId.andReturn('testId');
|
||||
|
||||
// Set up subscription...
|
||||
mockSubscription.getTelemetryObjects
|
||||
.andReturn([mockDomainObject]);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
// Fire subscription callback
|
||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||
|
||||
// ...and exposed the results for template to consume
|
||||
expect(controller.updated()).toEqual("1982-278 08:25:21.123Z");
|
||||
expect(controller.rangeValues().testId).toEqual(789);
|
||||
});
|
||||
|
||||
it("sorts domain objects by index", function () {
|
||||
var testIndexes = { a: 2, b: 1, c: 3, d: 0 },
|
||||
mockDomainObjects = Object.keys(testIndexes).sort().map(function (id) {
|
||||
var mockDomainObj = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
["getId", "getModel"]
|
||||
);
|
||||
|
||||
mockDomainObj.getId.andReturn(id);
|
||||
mockDomainObj.getModel.andReturn({ index: testIndexes[id] });
|
||||
|
||||
return mockDomainObj;
|
||||
});
|
||||
|
||||
// Expose those domain objects...
|
||||
mockSubscription.getTelemetryObjects.andReturn(mockDomainObjects);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
// Fire subscription callback
|
||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||
|
||||
// Controller should expose same objects, but sorted by index from model
|
||||
expect(controller.getTelemetryObjects()).toEqual([
|
||||
mockDomainObjects[3], // d, index=0
|
||||
mockDomainObjects[1], // b, index=1
|
||||
mockDomainObjects[0], // a, index=2
|
||||
mockDomainObjects[2] // c, index=3
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses a timeout to throttle update", function () {
|
||||
// Set up subscription...
|
||||
mockSubscription.getTelemetryObjects
|
||||
.andReturn([mockDomainObject]);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
// Set the object in view; should not need a timeout
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
expect(mockTimeout.calls.length).toEqual(0);
|
||||
|
||||
// Next call should schedule an update on a timeout
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
expect(mockTimeout.calls.length).toEqual(1);
|
||||
|
||||
// ...but this last one should not, since existing
|
||||
// timeout will cover it
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
expect(mockTimeout.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("allows changing column width", function () {
|
||||
var initialWidth = controller.columnWidth();
|
||||
controller.increaseColumnWidth();
|
||||
expect(controller.columnWidth()).toBeGreaterThan(initialWidth);
|
||||
});
|
||||
|
||||
describe("filter", function () {
|
||||
var doFilter,
|
||||
filteredObjects,
|
||||
filteredObjectNames;
|
||||
|
||||
beforeEach(function () {
|
||||
var telemetryObjects,
|
||||
updateFilteredObjects;
|
||||
|
||||
telemetryObjects = [
|
||||
'DEF123',
|
||||
'abc789',
|
||||
'456abc',
|
||||
'4ab3cdef',
|
||||
'hjs[12].*(){}^\\'
|
||||
].map(function (objectName, index) {
|
||||
var mockTelemetryObject = jasmine.createSpyObj(
|
||||
objectName,
|
||||
["getId", "getModel"]
|
||||
);
|
||||
|
||||
mockTelemetryObject.getId.andReturn(objectName);
|
||||
mockTelemetryObject.getModel.andReturn({
|
||||
name: objectName,
|
||||
index: index
|
||||
});
|
||||
|
||||
return mockTelemetryObject;
|
||||
});
|
||||
|
||||
mockSubscription
|
||||
.getTelemetryObjects
|
||||
.andReturn(telemetryObjects);
|
||||
|
||||
// Trigger domainObject change to create subscription.
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
updateFilteredObjects = function () {
|
||||
filteredObjects = controller.getTelemetryObjects();
|
||||
filteredObjectNames = filteredObjects.map(function (o) {
|
||||
return o.getModel().name;
|
||||
});
|
||||
};
|
||||
|
||||
doFilter = function (term) {
|
||||
controller.filter(term);
|
||||
// Filter is debounced so we have to force it to occur.
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
updateFilteredObjects();
|
||||
};
|
||||
|
||||
updateFilteredObjects();
|
||||
});
|
||||
|
||||
it("initially shows all objects", function () {
|
||||
expect(filteredObjectNames).toEqual([
|
||||
'DEF123',
|
||||
'abc789',
|
||||
'456abc',
|
||||
'4ab3cdef',
|
||||
'hjs[12].*(){}^\\'
|
||||
]);
|
||||
});
|
||||
|
||||
it("by blank string matches all objects", function () {
|
||||
doFilter('');
|
||||
expect(filteredObjectNames).toEqual([
|
||||
'DEF123',
|
||||
'abc789',
|
||||
'456abc',
|
||||
'4ab3cdef',
|
||||
'hjs[12].*(){}^\\'
|
||||
]);
|
||||
});
|
||||
|
||||
it("exactly matches an object name", function () {
|
||||
doFilter('4ab3cdef');
|
||||
expect(filteredObjectNames).toEqual(['4ab3cdef']);
|
||||
});
|
||||
|
||||
it("partially matches object names", function () {
|
||||
doFilter('abc');
|
||||
expect(filteredObjectNames).toEqual([
|
||||
'abc789',
|
||||
'456abc'
|
||||
]);
|
||||
});
|
||||
|
||||
it("matches case insensitive names", function () {
|
||||
doFilter('def');
|
||||
expect(filteredObjectNames).toEqual([
|
||||
'DEF123',
|
||||
'4ab3cdef'
|
||||
]);
|
||||
});
|
||||
|
||||
it("works as expected with special characters", function () {
|
||||
doFilter('[12]');
|
||||
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||
doFilter('.*');
|
||||
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||
doFilter('.*()');
|
||||
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||
doFilter('.*?');
|
||||
expect(filteredObjectNames).toEqual([]);
|
||||
doFilter('.+');
|
||||
expect(filteredObjectNames).toEqual([]);
|
||||
});
|
||||
|
||||
it("exposes CSS classes from limits", function () {
|
||||
var id = mockDomainObject.getId(),
|
||||
testClass = "some-css-class",
|
||||
mockLimitCapability =
|
||||
jasmine.createSpyObj('limit', ['evaluate']);
|
||||
|
||||
mockDomainObject.getCapability.andCallFake(function (key) {
|
||||
return key === 'limit' && mockLimitCapability;
|
||||
});
|
||||
mockLimitCapability.evaluate
|
||||
.andReturn({ cssClass: testClass });
|
||||
|
||||
mockSubscription.getTelemetryObjects
|
||||
.andReturn([mockDomainObject]);
|
||||
|
||||
fireFnWatches();
|
||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||
|
||||
expect(controller.classes()[id]).toEqual(testClass);
|
||||
});
|
||||
|
||||
it("exposes a counter that changes with each update", function () {
|
||||
var i, prior;
|
||||
|
||||
for (i = 0; i < 10; i += 1) {
|
||||
prior = controller.counter();
|
||||
expect(controller.counter()).toEqual(prior);
|
||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||
expect(controller.counter()).not.toEqual(prior);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
39
platform/features/autoflow/test/MCTAutoflowTableSpec.js
Executable file
39
platform/features/autoflow/test/MCTAutoflowTableSpec.js
Executable file
@@ -0,0 +1,39 @@
|
||||
|
||||
define(
|
||||
["../src/MCTAutoflowTable"],
|
||||
function (MCTAutoflowTable) {
|
||||
|
||||
describe("The mct-autoflow-table directive", function () {
|
||||
var mctAutoflowTable;
|
||||
|
||||
beforeEach(function () {
|
||||
mctAutoflowTable = new MCTAutoflowTable();
|
||||
});
|
||||
|
||||
// Real functionality is contained/tested in the linker,
|
||||
// so just check to make sure we're exposing the directive
|
||||
// appropriately.
|
||||
it("is applicable at the element level", function () {
|
||||
expect(mctAutoflowTable.restrict).toEqual("E");
|
||||
});
|
||||
|
||||
it("two-ways binds needed scope variables", function () {
|
||||
expect(mctAutoflowTable.scope).toEqual({
|
||||
objects: "=",
|
||||
values: "=",
|
||||
rows: "=",
|
||||
updated: "=",
|
||||
classes: "=",
|
||||
columnWidth: "=",
|
||||
counter: "="
|
||||
});
|
||||
});
|
||||
|
||||
it("provides a link function", function () {
|
||||
expect(mctAutoflowTable.link).toEqual(jasmine.any(Function));
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user