From a5c1c308cfa44bd99b906078ec650478a0da05dc Mon Sep 17 00:00:00 2001 From: Timur Sufiev Date: Thu, 25 Jun 2015 19:05:35 +0300 Subject: [PATCH] Decouple @enum and drop-down widget Provide a convenience fields.linkedcollection model to handle common use-case of using @ref in a Mistral WB. Cover it with unit-tests as well all scenarios of using fields.linkedcollection in MIstral WB. Change-Id: I97a61262db4cc521b5c230667a49b99701318f3f Closes-Bug: #1467514 --- .../mistral/js/mistral.workbook.models.js | 131 ++---------- .../test/js/workbook.controller.spec.js | 143 +++++++++++++ extensions/mistral/test/js/workbookSpec.js | 201 +++++++----------- karma-unit.conf.js | 3 +- merlin/static/merlin/js/merlin.directives.js | 16 -- .../static/merlin/js/merlin.field.models.js | 76 +++++-- .../merlin/templates/fields/choices.html | 11 +- .../merlin/templates/fields/number.html | 2 +- .../merlin/templates/fields/string.html | 2 +- .../static/merlin/templates/fields/text.html | 2 +- merlin/test/js/modelsSpec.js | 61 ++++++ 11 files changed, 374 insertions(+), 274 deletions(-) create mode 100644 extensions/mistral/test/js/workbook.controller.spec.js diff --git a/extensions/mistral/static/mistral/js/mistral.workbook.models.js b/extensions/mistral/static/mistral/js/mistral.workbook.models.js index d4b3a95..9a95df7 100644 --- a/extensions/mistral/static/mistral/js/mistral.workbook.models.js +++ b/extensions/mistral/static/mistral/js/mistral.workbook.models.js @@ -110,10 +110,10 @@ base.on('change', function(operation) { var argsEntry, pos, entry; if ( operation != 'id' ) { - pos = base._stdActions.getPosByID(base.get()); + pos = base._collection.getPosByID(base.get()); if ( pos > -1 ) { entry = self.get('base-input'); - argsEntry = base._stdActions.get(pos); + argsEntry = base._collection.get(pos); entry.resetKeys(argsEntry.toJSON()); } } @@ -122,44 +122,15 @@ } }, { 'base': { - '@class': fields.string.extend({ + '@class': fields.linkedcollection.extend({ create: function(json, parameters) { - var self = fields.string.create.call(this, json, parameters), - stdActionsCls = Barricade.create({ - '@type': String, - '@ref': { - to: function() { - return fields.StandardActions; - }, - needs: function() { - return models.Root; - }, - getter: function(data) { - return data.needed.get('standardActions'); - } - } - }); - - self._stdActions = stdActionsCls.create().on( - 'replace', function(newValue) { - self._stdActions = newValue; - self._stdActions.on('change', function() { - self._choices = self._stdActions.getIDs(); - self.resetValues(); - }); - self._stdActions.emit('change'); - }); - - return self; - }, - _choices: [] + parameters = Object.create(parameters); + parameters.toCls = models.StandardActions; + parameters.neededCls = models.Root; + parameters.substitutedEntryID = 'standardActions'; + return fields.linkedcollection.create.call(this, json, parameters); + } }, { - '@enum': function() { - if ( this._stdActions.isPlaceholder() ) { - this.emit('_resolveUp', this._stdActions); - } - return this._choices; - }, '@meta': { 'index': 1, 'row': 0 @@ -402,43 +373,15 @@ models.ActionTaskMixin = Barricade.Blueprint.create(function() { return this.extend({}, { 'action': { - '@class': fields.string.extend({ + '@class': fields.linkedcollection.extend({ create: function(json, parameters) { - var self = fields.string.create.call(this, json, parameters), - actionsCls = Barricade.create({ - '@type': String, - '@ref': { - to: function() { - return models.Actions; - }, - needs: function() { - return models.Workbook; - }, - getter: function(data) { - return data.needed.get('actions'); - } - } - }); - - self._actions = actionsCls.create().on( - 'replace', function(newValue) { - self._actions = newValue; - self._actions.on('change', function() { - self._choices = self._actions.getIDs(); - self.resetValues(); - }); - self._actions.emit('change'); - }); - return self; - }, - _choices: [] + parameters = Object.create(parameters); + parameters.toCls = models.Actions; + parameters.neededCls = models.Workbook; + parameters.substitutedEntryID = 'actions'; + return fields.linkedcollection.create.call(this, json, parameters); + } }, { - '@enum': function() { - if ( this._actions.isPlaceholder() ) { - this.emit('_resolveUp', this._actions); - } - return this._choices; - }, '@meta': { 'row': 0, 'index': 1 @@ -451,43 +394,15 @@ models.WorkflowTaskMixin = Barricade.Blueprint.create(function() { return this.extend({}, { 'workflow': { - '@class': fields.string.extend({ + '@class': fields.linkedcollection.extend({ create: function(json, parameters) { - var self = fields.string.create.call(this, json, parameters), - workflowsCls = Barricade.create({ - '@type': String, - '@ref': { - to: function() { - return models.Workflows; - }, - needs: function() { - return models.Workbook; - }, - getter: function(data) { - return data.needed.get('workflows'); - } - } - }); - - self._workflows = workflowsCls.create().on( - 'replace', function(newValue) { - self._workflows = newValue; - self._workflows.on('change', function() { - self._choices = self._workflows.getIDs(); - self.resetValues(); - }); - self._workflows.emit('change'); - }); - return self; - }, - _choices: [] + parameters = Object.create(parameters); + parameters.toCls = models.Workflows; + parameters.neededCls = models.Workbook; + parameters.substitutedEntryID = 'workflows'; + return fields.linkedcollection.create.call(this, json, parameters); + } }, { - '@enum': function() { - if ( this._workflows.isPlaceholder() ) { - this.emit('_resolveUp', this._workflows); - } - return this._choices; - }, '@meta': { 'row': 0, 'index': 1 diff --git a/extensions/mistral/test/js/workbook.controller.spec.js b/extensions/mistral/test/js/workbook.controller.spec.js new file mode 100644 index 0000000..92317fb --- /dev/null +++ b/extensions/mistral/test/js/workbook.controller.spec.js @@ -0,0 +1,143 @@ + +/* Copyright (c) 2015 Mirantis, Inc. + + 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. + */ +describe('together workbook model and controller', function() { + var models, utils, workbook; + + beforeEach(function () { + module('mistral'); + inject(function ($injector) { + models = $injector.get('mistral.workbook.models'); + utils = $injector.get('merlin.utils'); + }); + workbook = models.Workbook.create(); + }); + + + describe('define top-level actions available to user:', function () { + var $scope; + + beforeEach(inject(function (_$controller_) { + var $controller = _$controller_; + $scope = {}; + $controller('workbookCtrl', {$scope: $scope}); + $scope.workbook = workbook; + })); + + describe("'Add Action' action", function () { + it('adds a new Action', function () { + $scope.addAction(); + + expect(workbook.get('actions').get(0)).toBeDefined(); + }); + + it('creates action with predefined name', function () { + $scope.addAction(); + + expect(workbook.get('actions').get(0).getID()).toBeGreaterThan(''); + }); + + describe('', function () { + var actionID; + beforeEach(inject(function (baseActionID) { + actionID = baseActionID + '1'; + })); + + it("corresponding JSON has the right key for the Action", function () { + $scope.addAction(); + + expect(workbook.toJSON({pretty: true}).actions[actionID]).toBeDefined(); + }); + + it("once the Action ID is changed, it's reflected in JSON", function () { + var newID = 'action10'; + + $scope.addAction(); + workbook.get('actions').getByID(actionID).setID(newID); + + expect(workbook.toJSON({pretty: true}).actions[actionID]).toBeUndefined(); + expect(workbook.toJSON({pretty: true}).actions[newID]).toBeDefined(); + }); + + }); + + it('creates actions with different names on 2 successive calls', function () { + $scope.addAction(); + $scope.addAction(); + + expect(workbook.get('actions').get(0).getID()).not.toEqual( + workbook.get('actions').get(1).getID()) + }); + }); + + describe("'Add Workflow' action", function () { + it('adds a new Workflow', function () { + $scope.addWorkflow(); + + expect(workbook.get('workflows').get(0)).toBeDefined(); + }); + + describe('', function () { + var workflowID; + beforeEach(inject(function (baseWorkflowID) { + workflowID = baseWorkflowID + '1'; + })); + + it("corresponding JSON has the right key for the Workflow", function () { + $scope.addWorkflow(); + + expect(workbook.toJSON({pretty: true}).workflows[workflowID]).toBeDefined(); + }); + + it("once the workflow ID is changed, it's reflected in JSON", function () { + var newID = 'workflow10'; + + $scope.addWorkflow(); + workbook.get('workflows').getByID(workflowID).setID(newID); + + expect(workbook.toJSON({pretty: true}).workflows[workflowID]).toBeUndefined(); + expect(workbook.toJSON({pretty: true}).workflows[newID]).toBeDefined(); + }); + + }); + + it('creates workflow with predefined name', function () { + $scope.addWorkflow(); + + expect(workbook.get('workflows').get(0).getID()).toBeGreaterThan(''); + }); + + it('creates workflows with different names on 2 successive calls', function () { + $scope.addWorkflow(); + $scope.addWorkflow(); + + expect(workbook.get('workflows').get(0).getID()).not.toEqual( + workbook.get('workflows').get(1).getID()) + }); + + }); + + describe("'Create'/'Modify'/'Cancel' actions", function () { + it('edit causes a request to an api and a return to main page', function () { + + }); + + it('cancel causes just a return to main page', function () { + + }); + }); + + }) +}); \ No newline at end of file diff --git a/extensions/mistral/test/js/workbookSpec.js b/extensions/mistral/test/js/workbookSpec.js index 504ef25..5e80297 100644 --- a/extensions/mistral/test/js/workbookSpec.js +++ b/extensions/mistral/test/js/workbookSpec.js @@ -31,6 +31,37 @@ describe('workbook model logic', function() { return workbook.get('workflows').getByID(workflowID); } + describe('defines the standard actions getter for Action->Base field:', function() { + var root, action1; + + beforeEach(function() { + root = models.Root.create(); + root.set('workbook', workbook); + root.set('standardActions', { + 'nova.create_server': ['image', 'flavor', 'network_id'], + 'neutron.create_network': ['name', 'create_subnet'], + 'glance.create_image': ['image_url'] + }); + workbook.get('actions').add('action1'); + action1 = workbook.get('actions').getByID('action1'); + }); + + it('all actions are present as choices for the Base field', function() { + var availableActions = action1.get('base').getValues(); + + expect(availableActions).toEqual([ + 'nova.create_server', 'neutron.create_network', 'glance.create_image']); + }); + + it("'Base Input' field is set to have keys corresponding to 'Base' field value", function() { + action1.get('base').set('nova.create_server'); + expect(action1.get('base-input').getIDs()).toEqual(['image', 'flavor', 'network_id']); + + action1.get('base').set('neutron.create_network'); + expect(action1.get('base-input').getIDs()).toEqual(['name', 'create_subnet']); + }); + }); + describe('defines workflow structure transformations:', function() { var workflowID = 'workflow1'; @@ -71,7 +102,7 @@ describe('workbook model logic', function() { } beforeEach(function() { - workbook.get('workflows').push({name: 'Workflow 1'}, {id: workflowID}); + workbook.get('workflows').add(workflowID); }); describe('', function() { @@ -120,11 +151,30 @@ describe('workbook model logic', function() { expect(getTask(taskID).instanceof(models.DirectWFTask)).toBe(true); }); - it("changing task type from 'action' to 'workflow' causes proper structure changes", function() { - getTask(taskID).get('type').set('workflow'); + it("'action'-based task offers available custom actions for its Action field", function() { + workbook.get('actions').add('action1'); + expect(getTask(taskID).get('action').getValues()).toEqual(['action1']); - expect(getTask(taskID).instanceof(models.WorkflowTaskMixin)).toBe(true); - expect(getTask(taskID).instanceof(models.DirectWFTask)).toBe(true); + workbook.get('actions').add('action2'); + expect(getTask(taskID).get('action').getValues()).toEqual(['action1', 'action2']); + }); + + describe("changing task type from 'action' to 'workflow' causes", function() { + beforeEach(function() { + getTask(taskID).get('type').set('workflow'); + }); + + it('proper structure changes', function() { + expect(getTask(taskID).instanceof(models.WorkflowTaskMixin)).toBe(true); + expect(getTask(taskID).instanceof(models.DirectWFTask)).toBe(true); + }); + + it('and causes the Workflow field to suggest available workflows as choices', function() { + expect(getTask(taskID).get('workflow').getValues()).toEqual(['workflow1']); + + workbook.get('workflows').add('workflow2'); + expect(getTask(taskID).get('workflow').getValues()).toEqual([workflowID, 'workflow2']); + }); }); it("changing workflow type to 'reverse' causes the proper changes to its tasks", function() { @@ -168,11 +218,30 @@ describe('workbook model logic', function() { expect(getTask(taskID).instanceof(models.ReverseWFTask)).toBe(true); }); - it("changing task type from 'action' to 'workflow' causes proper structure changes", function() { - getTask(taskID).get('type').set('workflow'); + it("'action'-based task offers available custom actions for its Action field", function() { + workbook.get('actions').add('action1'); + expect(getTask(taskID).get('action').getValues()).toEqual(['action1']); - expect(getTask(taskID).instanceof(models.WorkflowTaskMixin)).toBe(true); - expect(getTask(taskID).instanceof(models.ReverseWFTask)).toBe(true); + workbook.get('actions').add('action2'); + expect(getTask(taskID).get('action').getValues()).toEqual(['action1', 'action2']); + }); + + describe("changing task type from 'action' to 'workflow' causes", function() { + beforeEach(function() { + getTask(taskID).get('type').set('workflow'); + }); + + it('proper structure changes', function() { + expect(getTask(taskID).instanceof(models.WorkflowTaskMixin)).toBe(true); + expect(getTask(taskID).instanceof(models.ReverseWFTask)).toBe(true); + }); + + it('and causes the Workflow field to suggest available workflows as choices', function() { + expect(getTask(taskID).get('workflow').getValues()).toEqual(['workflow1']); + + workbook.get('workflows').add('workflow2'); + expect(getTask(taskID).get('workflow').getValues()).toEqual([workflowID, 'workflow2']); + }); }); it("changing workflow type to 'direct' causes the proper changes to its tasks", function() { @@ -202,118 +271,4 @@ describe('workbook model logic', function() { }); }); - describe('defines top-level actions available to user:', function() { - var $scope; - - beforeEach(inject(function(_$controller_) { - var $controller = _$controller_; - $scope = {}; - $controller('workbookCtrl', {$scope: $scope}); - $scope.workbook = workbook; - })); - - describe("'Add Action' action", function() { - it('adds a new Action', function() { - $scope.addAction(); - - expect(workbook.get('actions').get(0)).toBeDefined(); - }); - - it('creates action with predefined name', function() { - $scope.addAction(); - - expect(workbook.get('actions').get(0).getID()).toBeGreaterThan(''); - }); - - describe('', function() { - var actionID; - beforeEach(inject(function(baseActionID) { - actionID = baseActionID + '1'; - })); - - it("corresponding JSON has the right key for the Action", function() { - $scope.addAction(); - - expect(workbook.toJSON({pretty: true}).actions[actionID]).toBeDefined(); - }); - - it("once the Action ID is changed, it's reflected in JSON", function() { - var newID = 'action10'; - - $scope.addAction(); - workbook.get('actions').getByID(actionID).setID(newID); - - expect(workbook.toJSON({pretty: true}).actions[actionID]).toBeUndefined(); - expect(workbook.toJSON({pretty: true}).actions[newID]).toBeDefined(); - }); - - }); - - it('creates actions with different names on 2 successive calls', function() { - $scope.addAction(); - $scope.addAction(); - - expect(workbook.get('actions').get(0).getID()).not.toEqual( - workbook.get('actions').get(1).getID()) - }); - }); - - describe("'Add Workflow' action", function() { - it('adds a new Workflow', function() { - $scope.addWorkflow(); - - expect(workbook.get('workflows').get(0)).toBeDefined(); - }); - - describe('', function() { - var workflowID; - beforeEach(inject(function(baseWorkflowID) { - workflowID = baseWorkflowID + '1'; - })); - - it("corresponding JSON has the right key for the Workflow", function() { - $scope.addWorkflow(); - - expect(workbook.toJSON({pretty: true}).workflows[workflowID]).toBeDefined(); - }); - - it("once the workflow ID is changed, it's reflected in JSON", function() { - var newID = 'workflow10'; - - $scope.addWorkflow(); - workbook.get('workflows').getByID(workflowID).setID(newID); - - expect(workbook.toJSON({pretty: true}).workflows[workflowID]).toBeUndefined(); - expect(workbook.toJSON({pretty: true}).workflows[newID]).toBeDefined(); - }); - - }); - - it('creates workflow with predefined name', function() { - $scope.addWorkflow(); - - expect(workbook.get('workflows').get(0).getID()).toBeGreaterThan(''); - }); - - it('creates workflows with different names on 2 successive calls', function() { - $scope.addWorkflow(); - $scope.addWorkflow(); - - expect(workbook.get('workflows').get(0).getID()).not.toEqual( - workbook.get('workflows').get(1).getID()) - }); - - }); - - describe("'Create'/'Modify'/'Cancel' actions", function() { - it('edit causes a request to an api and a return to main page', function() { - - }); - - it('cancel causes just a return to main page', function() { - - }); - }); - - }) }); diff --git a/karma-unit.conf.js b/karma-unit.conf.js index 87b08ad..1990664 100644 --- a/karma-unit.conf.js +++ b/karma-unit.conf.js @@ -52,7 +52,8 @@ module.exports = function (config) { // explicitly require first module definition file to avoid errors 'extensions/mistral/static/mistral/js/mistral.init.js', 'extensions/mistral/static/mistral/js/mistral.*.js', - 'extensions/mistral/test/js/*Spec.js' + 'extensions/mistral/test/js/*Spec.js', + 'extensions/mistral/test/js/*spec.js' ], preprocessors: { diff --git a/merlin/static/merlin/js/merlin.directives.js b/merlin/static/merlin/js/merlin.directives.js index 5f0f306..ef1ad6d 100644 --- a/merlin/static/merlin/js/merlin.directives.js +++ b/merlin/static/merlin/js/merlin.directives.js @@ -134,18 +134,6 @@ }) .directive('typedField', ['$compile', 'merlin.templates', function($compile, templates) { - function updateAutoCompletionDirective(template) { - template.find('input').each(function(index, elem) { - elem = angular.element(elem); - if ( elem.attr('autocompletable') ) { - // process 'autocompletable' attribute only once - elem.removeAttr('autocompletable'); - elem.attr('typeahead-editable', true); - elem.attr('typeahead', - "option for option in value.getSuggestions() | filter:$viewValue"); - } - }); - } return { restrict: 'E', scope: { @@ -154,10 +142,6 @@ }, link: function(scope, element) { templates.templateReady(scope.type).then(function(template) { - template = angular.element(template); - if ( scope.value.getSuggestions ) { - updateAutoCompletionDirective(template); - } element.replaceWith($compile(template)(scope)); }) } diff --git a/merlin/static/merlin/js/merlin.field.models.js b/merlin/static/merlin/js/merlin.field.models.js index 6be62f3..4f84d7a 100644 --- a/merlin/static/merlin/js/merlin.field.models.js +++ b/merlin/static/merlin/js/merlin.field.models.js @@ -10,9 +10,10 @@ return this; }); - var restrictedChoicesMixin = Barricade.Blueprint.create(function() { + var viewChoicesMixin = Barricade.Blueprint.create(function() { var self = this, - values, labels, items; + dropDownLimit = this._dropDownLimit || 5, + values, labels, items, isDropDown; function fillItems() { values = self.getEnumValues(); @@ -42,6 +43,15 @@ values = undefined; }; + this.isDropDown = function() { + // what starts its life as being dropdown / not being dropdown + // should remain so forever + if ( angular.isUndefined(isDropDown) ) { + isDropDown = !this.isEmpty() && this.getValues().length < dropDownLimit; + } + return isDropDown; + }; + this.setType('choices'); return this; }); @@ -92,11 +102,7 @@ }; wildcardMixin.call(this); if ( this.getEnumValues ) { - restrictedChoicesMixin.call(this); - } - var autocompletionUrl = utils.getMeta(this, 'autocompletionUrl'); - if ( autocompletionUrl ) { - autoCompletionMixin.call(this, autocompletionUrl); + viewChoicesMixin.call(this); } return this; }); @@ -114,19 +120,6 @@ } }, {'@type': String}); - var autoCompletionMixin = Barricade.Blueprint.create(function(url) { - var self = this; - - this.getSuggestions = function() { return []; }; - $http.get(url).success(function(data) { - self.getSuggestions = function() { - return data; - }; - }); - - return this; - }); - var textModel = Barricade.Primitive.extend({ create: function(json, parameters) { var self = Barricade.Primitive.create.call(this, json, parameters); @@ -256,14 +249,55 @@ } }, {'@type': Object}); + var linkedCollectionModel = stringModel.extend({ + create: function(json, parameters) { + var self = stringModel.create.call(this, json, parameters), + collectionCls = Barricade.create({ + '@type': String, + '@ref': { + to: function() { + return parameters.toCls; + }, + needs: function() { + return parameters.neededCls; + }, + getter: function(data) { + return data.needed.get(parameters.substitutedEntryID); + } + } + }); + + self._collection = collectionCls.create().on( + 'replace', function(newValue) { + self._collection = newValue; + self._collection.on('change', function() { + self._choices = self._collection.getIDs(); + self.resetValues(); + }); + self._collection.emit('change'); + }); + + return self; + }, + _choices: [] + }, { + '@enum': function() { + if ( this._collection.isPlaceholder() ) { + this.emit('_resolveUp', this._collection); + } + return this._choices; + } + } + ); + return { string: stringModel, text: textModel, number: numberModel, list: listModel, + linkedcollection: linkedCollectionModel, dictionary: dictionaryModel, frozendict: frozendictModel, - autocompletionmixin: autoCompletionMixin, wildcard: wildcardMixin // use for most general type-checks }; }]) diff --git a/merlin/static/merlin/templates/fields/choices.html b/merlin/static/merlin/templates/fields/choices.html index c74e739..6f1b25f 100644 --- a/merlin/static/merlin/templates/fields/choices.html +++ b/merlin/static/merlin/templates/fields/choices.html @@ -1,9 +1,16 @@
- - + +
{$ error $}
diff --git a/merlin/static/merlin/templates/fields/number.html b/merlin/static/merlin/templates/fields/number.html index fbcc2b2..b0b10e1 100644 --- a/merlin/static/merlin/templates/fields/number.html +++ b/merlin/static/merlin/templates/fields/number.html @@ -3,6 +3,6 @@ + validatable-with="value">
{$ error $}
diff --git a/merlin/static/merlin/templates/fields/string.html b/merlin/static/merlin/templates/fields/string.html index b526869..a6a1978 100644 --- a/merlin/static/merlin/templates/fields/string.html +++ b/merlin/static/merlin/templates/fields/string.html @@ -2,6 +2,6 @@ + validatable-with="value">
{$ error $}
diff --git a/merlin/static/merlin/templates/fields/text.html b/merlin/static/merlin/templates/fields/text.html index 24173c3..f873ba7 100644 --- a/merlin/static/merlin/templates/fields/text.html +++ b/merlin/static/merlin/templates/fields/text.html @@ -2,6 +2,6 @@ + validatable-with="value">
{$ error $}
diff --git a/merlin/test/js/modelsSpec.js b/merlin/test/js/modelsSpec.js index cd0cbc0..db120c5 100644 --- a/merlin/test/js/modelsSpec.js +++ b/merlin/test/js/modelsSpec.js @@ -141,4 +141,65 @@ describe('merlin models:', function() { }); }); + + describe('linkedCollection field', function() { + var collectionCls, linkedObjCls, linkedObj, lnkField; + + beforeEach(function() { + collectionCls = fields.dictionary.extend({}, { + '?': { + '@class': fields.string + } + }); + linkedObjCls = fields.frozendict.extend({}, { + 'realCollection': { + '@class': collectionCls + }, + 'linkedField': { + '@class': fields.linkedcollection.extend({ + create: function(json, parameters) { + parameters = Object.create(parameters); + parameters.toCls = collectionCls; + parameters.neededCls = linkedObjCls; + parameters.substitutedEntryID = 'realCollection'; + return fields.linkedcollection.create.call(this, json, parameters); + }, + _dropDownLimit: 4 + }) + } + }); + linkedObj = linkedObjCls.create({'realCollection': {'a': '', 'b': ''}}); + lnkField = linkedObj.get('linkedField'); + }); + + it('provides access from @enum values of one field to IDs of another one', function() { + expect(lnkField.getValues()).toEqual(['a', 'b']); + + linkedObj.get('realCollection').add('c'); + expect(lnkField.getValues()).toEqual(['a', 'b', 'c']); + }); + + describe('and exposes _collection attribute', function() { + it('in case more complex things need to be done', function() { + expect(lnkField._collection).toBeDefined(); + }); + + it("which is truly initialized after first @enum's .getValues() call", function() { + expect(lnkField._collection.isPlaceholder()).toBe(true); + + lnkField.getValues(); + expect(lnkField._collection.isPlaceholder()).toBe(false); + expect(lnkField._collection).toBe(linkedObj.get('realCollection')); + }); + }); + + describe('exposes .isDropDown() call due to @enum presense', function() { + it('which always returns false due to deferred nature of linkedField', function() { + expect(lnkField.isDropDown()).toBe(false); + + lnkField.getValues(); + expect(lnkField.isDropDown()).toBe(false); + }); + }); + }); }); \ No newline at end of file