+
diff --git a/merlin/static/merlin/templates/fields/number.html b/merlin/static/merlin/templates/fields/number.html
index b0b10e1..3f06840 100644
--- a/merlin/static/merlin/templates/fields/number.html
+++ b/merlin/static/merlin/templates/fields/number.html
@@ -1,8 +1,4 @@
-
+
+
{$ error $}
diff --git a/merlin/static/merlin/templates/fields/string.html b/merlin/static/merlin/templates/fields/string.html
index a6a1978..619bdc8 100644
--- a/merlin/static/merlin/templates/fields/string.html
+++ b/merlin/static/merlin/templates/fields/string.html
@@ -1,7 +1,4 @@
-
+
+
{$ error $}
diff --git a/merlin/static/merlin/templates/fields/text.html b/merlin/static/merlin/templates/fields/text.html
index f873ba7..41960e2 100644
--- a/merlin/static/merlin/templates/fields/text.html
+++ b/merlin/static/merlin/templates/fields/text.html
@@ -1,7 +1,4 @@
-
+
+
{$ error $}
diff --git a/merlin/static/merlin/templates/labeled.html b/merlin/static/merlin/templates/labeled.html
new file mode 100644
index 0000000..f011b06
--- /dev/null
+++ b/merlin/static/merlin/templates/labeled.html
@@ -0,0 +1,4 @@
+
diff --git a/merlin/test/js/merlin.directives.spec.js b/merlin/test/js/merlin.directives.spec.js
index 7a3df8e..1b7ede8 100644
--- a/merlin/test/js/merlin.directives.spec.js
+++ b/merlin/test/js/merlin.directives.spec.js
@@ -66,7 +66,7 @@ describe('merlin directives', function() {
return element;
}
- it('shows panel heading when and only when its title() is not false', function() {
+ it('shows panel heading when and only when its title is defined', function() {
var title = 'My Panel',
element1, element2;
diff --git a/merlin/test/js/merlin.filters.spec.js b/merlin/test/js/merlin.filters.spec.js
index 91102f9..3125956 100644
--- a/merlin/test/js/merlin.filters.spec.js
+++ b/merlin/test/js/merlin.filters.spec.js
@@ -28,778 +28,434 @@ describe('merlin filters', function() {
});
describe('extractPanels() behavior:', function() {
- var extractPanels, simpleMerlinObjClass, simpleMerlinObjClassWithMeta;
+ var extractPanels, simpleContainerCls, complexContainerCls;
beforeEach(function() {
extractPanels = $filter('extractPanels');
- simpleMerlinObjClass = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string
- },
- 'key2': {
- '@class': fields.string
- }
+ simpleContainerCls = fields.frozendict.extend({}, {
+ 'key1': {'@class': fields.string},
+ 'key2': {'@class': fields.string}
});
- simpleMerlinObjClassWithMeta = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.number.extend({}, {
- '@meta': {
- 'panelIndex': 0
- }
- })
- },
- 'key2': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 0
- }
- })
- },
- 'key3': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 1
- }
+ complexContainerCls = fields.frozendict.extend({}, {
+ 'numberKey': {'@class': fields.number},
+ 'stringKey': {'@class': fields.string},
+ 'containerKey': {
+ '@class': fields.dictionary.extend({}, {
+ '?': {'@class': simpleContainerCls}
})
}
});
});
- it('works properly only with objects created from Merlin classes', function() {
- var simpleBarricadeObjClass = Barricade.create({
- '@type': Object,
- 'key1': {
- '@type': Number
- },
- 'key2': {
- '@type': String
+ describe('filter input expectations', function() {
+ it('2 arguments are required: Barricade.Container subclass and a keyExtractor function', function() {
+ var atomicField = fields.string.create();
+ var nonAtomicField = simpleContainerCls.create();
+ function extractor() {}
+ function wrongCall1() {
+ return extractPanels(atomicField, extractor);
+ }
+ function wrongCall2() {
+ return extractPanels(nonAtomicField);
+ }
+ function properCall() {
+ return extractPanels(nonAtomicField, extractor);
}
- }),
- simpleBarricadeObj = simpleBarricadeObjClass.create(),
- simpleMerlinObj = simpleMerlinObjClass.create();
- expect(function() {
- return extractPanels(simpleBarricadeObj);
- }).toThrow();
-
- expect(function() {
- return extractPanels(simpleMerlinObj);
- }).not.toThrow();
- });
-
- describe('the filter relies upon `@meta` object with `panelIndex` key', function() {
- it('and all fields without it are merged into a single panel', function() {
- var simpleObj = simpleMerlinObjClass.create(),
- panels = extractPanels(simpleObj);
-
- expect(panels.length).toBe(1);
- });
-
- it('each entry with the same panelIndex is placed in the same panel', function() {
- var simpleObj = simpleMerlinObjClassWithMeta.create(),
- panels = extractPanels(simpleObj);
-
- expect(panels.length).toBe(2);
- expect(panels[0].items.length).toBe(2);
- expect(panels[1].items.length).toBe(1);
- });
-
- it('the filter is applied only to the top-level entries of the passed object', function() {
- var merlinObjWithNestedPanelIndices = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.number.extend({}, {
- '@meta': {
- 'panelIndex': 0
- }
- })
- },
- 'key2': {
- '@class': fields.frozendict.extend({}, {
- 'key3': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 1
- }
- })
- },
- '@meta': {
- 'panelIndex': 0
- }
- })
- }
- }).create(),
- panels = extractPanels(merlinObjWithNestedPanelIndices);
-
- expect(panels.length).toBe(1);
+ expect(wrongCall1).toThrow();
+ expect(wrongCall2).toThrow();
+ expect(properCall).not.toThrow();
});
});
- describe('panels generated from Barricade.MutableObject (non-permanent panels)', function() {
- var topLevelObj;
+ describe('filter output minimal guarantees', function() {
+ var panels, container;
+ function extractor(field) {
+ return field.instanceof(fields.string) ? 0 : null;
+ }
beforeEach(function() {
- topLevelObj = fields.frozendict.extend({}, {
- 'key2': {
- '@class': fields.dictionary.extend({}, {
- '@meta': {
- 'panelIndex': 0
- },
- '?': {
- '@class': fields.frozendict.extend({}, {
- 'name': {'@class': fields.string}
- })
- }
- })
- }
- }).create();
+ container = simpleContainerCls.create({'key1': 'first', 'key2': 'second'});
+ panels = extractPanels(container, extractor);
});
- it('are given a separate panel for each MutableObject entry', function() {
- var panels;
- topLevelObj.set('key2', {
- 'id1': {'name': 'String1'},
- 'id2': {'name': 'String2'}
- });
- panels = extractPanels(topLevelObj);
-
- expect(panels.length).toBe(2);
+ it('a panel in an output provides .each() method', function() {
+ expect(panels[0].each).toBeDefined();
});
- describe('', function() {
- var panels;
-
- beforeEach(function() {
- topLevelObj.set('key2', {'id1': {'name': 'some name'}});
- panels = extractPanels(topLevelObj);
- });
-
- it('have their title exposed via .title() which mirrors their id', function() {
- expect(panels[0].title()).toBe('id1');
- });
-
- it("panel's title() acts also as a setter of the underlying object id", function() {
- panels[0].title('id2');
-
- expect(panels[0].title()).toBe('id2');
- expect(topLevelObj.get('key2').getByID('id2')).toBeDefined();
- });
-
- it('are removable (thus are not permanent)', function() {
- expect(panels[0].removable).toBe(true);
- });
-
- it('remove() function actually removes a panel', function() {
- panels[0].remove();
- panels = extractPanels(topLevelObj);
-
- expect(panels.length).toBe(0);
+ it('.each() method could be used for panel contents enumeration', function() {
+ var fields = {};
+ panels[0].each(function(key, field) {
+ fields[key] = field;
});
+ expect(fields.key1.get()).toEqual('first');
+ expect(fields.key2.get()).toEqual('second');
});
-
});
- describe('panels generated from objects other than Barricade.MutableObject (permanent panels)', function() {
- it('have fields marked with the same `panelIndex` in the one panel', function() {
- var immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 23
- }
- })
- },
- 'key2': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 23
- }
- })
- },
- 'key3': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 25
- }
- })
+ describe('keyExtractor function expectations', function() {
+ var collectedFields, container, panels;
+ beforeEach(function() {
+ collectedFields = {};
+ });
+
+ it('only fields for which it returns a numeric value get to the panel', function() {
+ container = simpleContainerCls.create({'key1': 'first', 'key2': 'second'});
+ function extractor(field) {
+ return field.get() == 'first' && 1;
+ }
+
+ panels = extractPanels(container, extractor);
+ panels[0].each(function(key, field) {
+ collectedFields[key] = field;
+ });
+
+ expect(collectedFields.key1).toBeDefined();
+ expect(collectedFields.key2).not.toBeDefined();
+ });
+
+ it('container fields yielding numeric value get to panel as is', function() {
+ container = complexContainerCls.create({
+ 'containerKey': {'container1': {'key1': 'first', 'key2': 'second'}}
+ });
+ function extractor(field) {
+ return field.instanceof(simpleContainerCls) && 1;
+ }
+
+ panels = extractPanels(container, extractor);
+ panels[0].each(function(key, field) {
+ collectedFields[key] = field;
+ });
+
+ expect(panels.length).toBe(1);
+ expect(collectedFields.key1.get()).toEqual('first');
+ expect(collectedFields.key2.get()).toEqual('second');
+
+ });
+
+ it('same numeric value puts the fields into the same (aggregate) panel', function() {
+ container = complexContainerCls.create(
+ {
+ 'numberKey': 10,
+ 'stringKey': 'some'
+ });
+
+ function extractor(field) {
+ return field.instanceof(simpleContainerCls) ? null : 1;
+ }
+
+ panels = extractPanels(container, extractor);
+ panels[0].each(function(key, field) {
+ collectedFields[key] = field;
+ });
+
+ expect(panels.length).toBe(1);
+ expect(collectedFields.numberKey.get()).toEqual(10);
+ expect(collectedFields.stringKey.get()).toEqual('some');
+ });
+
+ it('panels are ordered by extracted numeric value', function() {
+ collectedFields = [];
+ container = complexContainerCls.create({
+ 'numberKey': 10,
+ 'stringKey': 'some'
+ });
+ function extractor(field) {
+ if (field.instanceof(fields.number)) {
+ return 1;
+ } else if (field.instanceof(fields.string)) {
+ return 2;
}
- }).create(),
- panels = extractPanels(immutableObj);
+ }
+
+ panels = extractPanels(container, extractor);
+ panels.forEach(function(panel, index) {
+ collectedFields[index] = {};
+ panel.each(function(key, field) {
+ collectedFields[index][key] = field;
+ });
+ });
expect(panels.length).toBe(2);
- expect(panels[0].items.panelIndex).toBe(23);
-
+ expect(collectedFields[0].numberKey.get()).toEqual(10);
+ expect(collectedFields[1].stringKey.get()).toEqual('some');
});
- it('number of panels is defined by number of different `panelIndex` keys', function() {
- var immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 23
- }
- })
- },
- 'key2': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 24
- }
- })
- },
- 'key3': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 25
- }
- })
+ it('second argument to extractor is the parent container', function() {
+ collectedFields = [];
+ container = complexContainerCls.create({
+ 'containerKey': {
+ 'container1': {'key1': 'first', 'key2': 'second'},
+ 'container2': {'key1': 'third', 'key2': 'fourth'}
}
- }).create(),
- panels = extractPanels(immutableObj);
+ });
+ function extractor(field, parent) {
+ if (field.instanceof(simpleContainerCls)) {
+ return 10 + parent.toArray().indexOf(field);
+ } else if (field.instanceof(Barricade.Container)) {
+ return null;
+ }
+ }
- expect(panels.length).toBe(3);
+ panels = extractPanels(container, extractor);
+ panels.forEach(function(panel, index) {
+ collectedFields[index] = {};
+ panel.each(function(key, field) {
+ collectedFields[index][key] = field;
+ });
+ });
+
+ expect(panels.length).toBe(2);
+ expect(collectedFields[0].key1.get()).toEqual('first');
+ expect(collectedFields[0].key2.get()).toEqual('second');
+ expect(collectedFields[1].key1.get()).toEqual('third');
+ expect(collectedFields[1].key2.get()).toEqual('fourth');
});
-
- it('are ordered by the `panelIndex` ascension', function() {
- var immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 25
- }
- })
- },
- 'key2': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 24
- }
- })
- },
- 'key3': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 23
- }
- })
- }
- }).create(),
- panels = extractPanels(immutableObj);
-
- expect(panels[0].items.panelIndex).toBe(23);
- expect(panels[1].items.panelIndex).toBe(24);
- expect(panels[2].items.panelIndex).toBe(25);
- });
-
- it('have no title returned from .getTitle()', function() {
- var immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 25
- }
- })
- }
- }).create(),
- panels = extractPanels(immutableObj);
-
- expect(panels[0].title()).toBeUndefined();
- });
-
- it('are not removable (thus are permanent)', function() {
- var immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 25
- }
- })
- }
- }).create(),
- panels = extractPanels(immutableObj);
-
- expect(panels[0].removable).toBeUndefined();
- })
-
});
describe('panels are cached,', function() {
- var immutableObj;
-
+ var container, panels1, panels2;
beforeEach(function() {
- immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string
+ container = complexContainerCls.create({
+ 'numberKey': 10,
+ 'stringKey': 'some',
+ 'containerKey': {
+ 'container1': {'key1': 'first', 'key2': 'second'},
+ 'container2': {'key1': 'third', 'key2': 'fourth'}
}
- }).create();
+ });
+
});
it('and 2 consequent filter calls return the identical results', function() {
- var panels1, panels2;
+ function extractor(field, parent) {
+ if (field.instanceof(simpleContainerCls)) {
+ return 10 + parent.toArray().indexOf(field);
+ } else if (field.instanceof(Barricade.Container)) {
+ return null;
+ } else {
+ return 0;
+ }
+ }
- immutableObj.get('key1').set('String_1');
- panels1 = extractPanels(immutableObj);
- panels2 = extractPanels(immutableObj);
+ panels1 = extractPanels(container, extractor);
+ panels2 = extractPanels(container, extractor);
expect(panels1).toBe(panels2);
});
- it("still totally replacing the elements that go to permanent panels doesn't reset the cache", function() {
- var panels1, panels2;
-
- immutableObj.get('key1').set('String_1');
- panels1 = extractPanels(immutableObj);
- immutableObj.get('key1').set('String_2');
- panels2 = extractPanels(immutableObj);
-
- expect(panels1).toBe(panels2);
- });
-
- it('while totally replacing the top-level object of a non-permanent panel resets the cache', function() {
- var immutableObj = fields.frozendict.extend({}, {
- 'key2': {
- '@class': fields.dictionary.extend({}, {
- '@meta': {
- 'panelIndex': 0
- },
- '?': {
- '@class': fields.frozendict.extend({}, {
- 'key1': {'@class': fields.string}
- })
- }
- })
- }
- }).create(),
- panels1, panels2;
-
- immutableObj.set('key2', {'id_1': {key1: 'String_1'}});
- panels1 = extractPanels(immutableObj);
-
- immutableObj.get('key2').removeItem('id_1');
- immutableObj.set('key2', {'id_1': {key1: 'String_1'}});
- panels2 = extractPanels(immutableObj);
-
- expect(panels1).not.toBe(panels2);
- });
-
- it("but totally replacing the object contained within top-level object of a " +
- "non-permanent panel doesn't reset the cache", function() {
- var immutableObj = fields.frozendict.extend({}, {
- 'key2': {
- '@class': fields.dictionary.extend({}, {
- '@meta': {
- 'panelIndex': 0
- },
- '?': {
- '@class': fields.frozendict.extend({}, {
- 'key1': {'@class': fields.string}
- })
- }
- })
- }
- }).create(),
- panels1, panels2;
-
- immutableObj.set('key2', {'id_1': {key1: 'String_1'}});
- panels1 = extractPanels(immutableObj);
-
- immutableObj.get('key2').getByID('id_1').get('key1').set('String_2');
- panels2 = extractPanels(immutableObj);
-
- expect(panels1).toBe(panels2);
- });
-
- });
-
- });
-
- describe('extractRows() behavior:', function() {
- var extractRows, extractPanels;
-
- beforeEach(function() {
- extractPanels = $filter('extractPanels');
- extractRows = $filter('extractRows');
- });
-
- describe('the filter is meant to be chainable', function() {
- var immutableObj;
-
- beforeEach(function() {
- immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.number.extend({}, {
- '@meta': {
- 'row': 0
- }
- })
- }
- }).create();
- });
-
- it('with extractPanels() results', function() {
- var firstPanel = extractPanels(immutableObj)[0],
- rows = extractRows(firstPanel);
-
- expect(rows.length).toBe(1);
- });
-
- it('with Barricade.ImmutableObject contents', function() {
- var rows = extractRows(immutableObj);
-
- expect(rows.length).toBe(1);
- });
-
- it('even with Barricade.MutableObject contents', function() {
- var mutableObj = fields.dictionary.extend({}, {
- '?': {
- '@class': fields.string.extend({}, {
- '@meta': {'row': 0}
- })
+ describe('yet adding/removing entity tracked by keyExtractor causes panels recalculation', function() {
+ it('the change is being tracked by extractor', function() {
+ function extractor(field, parent) {
+ if (field.instanceof(simpleContainerCls)) {
+ return 10 + parent.toArray().indexOf(field);
+ } else if (field.instanceof(Barricade.Container)) {
+ return null;
+ } else {
+ return 0;
}
- }).create(),
- rows;
+ }
- mutableObj.push('string1', {id: 'id1'});
- mutableObj.push('string2', {id: 'id2'});
- rows = extractRows(mutableObj);
+ panels1 = extractPanels(container, extractor);
+ container.get('containerKey').push(
+ {'key1': 'fifth', 'key2': 'sixth'}, {id: 'container3'});
+ panels2 = extractPanels(container, extractor);
- expect(rows.length).toBe(1);
+ expect(panels1).not.toBe(panels2);
+ });
+
+ it('the same change is not being tracked by a different extractor', function() {
+ function extractor(field) {
+ if (field.instanceof(fields.dictionary)) {
+ return 10;
+ } else if (field.instanceof(Barricade.Container)) {
+ return null;
+ } else {
+ return 0;
+ }
+ }
+
+ panels1 = extractPanels(container, extractor);
+ container.get('containerKey').push(
+ {'key1': 'fifth', 'key2': 'sixth'}, {id: 'container3'});
+ panels2 = extractPanels(container, extractor);
+
+ expect(panels1).toBe(panels2);
+ });
});
-
});
- it("the filter is not meant to be chainable with Barricade " +
- "objects other MutableObject or ImmutableObject", function() {
- function test() {
- var arrayObj = fields.list.extend({}, {
- '*': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 0
- }
- })
- }
- }).create();
+ describe('other properties', function() {
+ var simpleContainerCls1, simpleContainerCls2,
+ complexContainerCls, obj, panels;
- arrayObj.push('string1');
- arrayObj.push('string2');
- return extractRows(arrayObj);
+ function extractor(field, parent) {
+ var key;
+ if (field.instanceof(simpleContainerCls1)) {
+ return 20 + parent.toArray().indexOf(field);
+ } else if (field.instanceof(simpleContainerCls2)) {
+ key = parent.getKeys()[0];
+ parent.each(function(itemKey, item) {
+ if (item === field) {
+ key = itemKey;
+ }
+ });
+ return 10 + parent.getKeys().indexOf(key);
+ } else if (field.instanceof(Barricade.Container)) {
+ return null;
+ } else {
+ return 0;
+ }
}
- expect(test).toThrow();
- });
+ beforeEach(function() {
+ simpleContainerCls1 = fields.frozendict.extend({}, {
+ 'key1': {'@class': fields.string},
+ 'key2': {'@class': fields.string}
+ });
+ simpleContainerCls2 = fields.frozendict.extend({}, {
+ 'key1': {'@class': fields.string},
+ 'key2': {'@class': fields.string}
+ });
- describe('the filter relies upon `@meta` object with `row` key', function() {
- it('and all fields without it are put into the same row', function() {
- var immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string
- },
- 'key2': {
- '@class': fields.string
- }
- }).create(),
- rows = extractRows(immutableObj);
-
- expect(rows.length).toBe(1);
- });
-
- it('the filter is applied only to the top-level entries of the passed object', function() {
- var immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 1
- }
+ complexContainerCls = fields.frozendict.extend({}, {
+ 'numberKey': {'@class': fields.number},
+ 'stringKey': {'@class': fields.string},
+ 'fluidContainer': {
+ '@class': fields.dictionary.extend({}, {
+ '?': {'@class': simpleContainerCls1}
})
},
- 'key2': {
+ 'fixedContainer': {
'@class': fields.frozendict.extend({}, {
- '@meta': {
- 'row': 2
- },
- 'key3': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 3
- }
- })
- }
+ 'container2': {'@class': simpleContainerCls2}
})
}
- }).create(),
- rows = extractRows(immutableObj);
-
- expect(rows.length).toBe(2);
- });
-
- it('2 fields with the same `row` key are placed in the same row', function() {
- var immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 0
- }
- })
- },
- 'key2': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 0
- }
- })
- }
- }).create(),
- rows = extractRows(immutableObj);
-
- expect(rows.length).toBe(1);
- });
-
- it('rows are ordered by the `row` key ascension', function() {
- var immutableObj = fields.frozendict.extend({}, {
- 'key3': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 2
- }
- })
- },
- 'key2': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 1
- }
- })
- },
- 'key1': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 0
- }
- })
- }
- }).create({key1: 'String_1', key2: 'String_2', key3: 'String_3'}),
- rows = extractRows(immutableObj);
-
- expect(rows[0].items[0].get()).toBe('String_1');
- expect(rows[1].items[0].get()).toBe('String_2');
- expect(rows[2].items[0].get()).toBe('String_3');
- });
-
- });
-
- describe('rows are cached,', function() {
- var immutableObj;
-
- beforeEach(function () {
- immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.frozendict.extend({}, {
- 'key2': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 1
- }
- })
- },
- 'key3': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 1
- }
- })
- }
- })
- }
- }).create({'key1': {'key2': 'string1', 'key3': 'string2'}});
- });
-
- it('and 2 consequent filter calls return the identical results', function () {
- var panels = extractPanels(immutableObj),
- rows1 = extractRows(panels[0]),
- rows2 = extractRows(panels[0]);
-
- expect(rows1).toBe(rows2);
- });
-
- describe('but totally replacing one of the elements that are contained within', function () {
- it("panel resets the cache", function () {
- var panels = extractPanels(immutableObj),
- rows1 = extractRows(panels[0]),
- rows2;
-
- immutableObj.set('key1', {'key2': 'string1', 'key3': 'string2'});
- panels = extractPanels(immutableObj);
- rows2 = extractRows(panels[0]);
-
- expect(rows2).not.toBe(rows1);
});
- it("ImmutableObject resets the cache", function () {
- var obj = immutableObj.get('key1'),
- rows1 = extractRows(obj),
- rows2;
-
- obj.set('key2', 'string5');
- rows2 = extractRows(obj);
-
- expect(rows2).not.toBe(rows1);
+ obj = complexContainerCls.create({
+ 'numberKey': 10,
+ 'stringKey': 'a string',
+ 'fluidContainer': {
+ 'container1': {'key1': 'one', 'key2': 'two'}
+ },
+ 'fixedContainer': {
+ 'container2': {'key1': 'one', 'key2': 'two'}
+ }
});
- it("MutableObject resets the cache", function () {
- var mutableObj = fields.dictionary.extend({}, {
- '?': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 0
- }
- })
- }
- }).create({'id1': 'string1', 'id2': 'string2'}),
- rows1 = extractRows(mutableObj),
- rows2;
+ panels = extractPanels(obj, extractor);
+ });
- mutableObj.removeItem('id1');
- mutableObj.push('string1', {id: 'id1'});
- rows2 = extractRows(mutableObj);
-
- expect(rows2).not.toBe(rows1);
+ describe('panel title', function() {
+ it('title is undefined for aggregate panels', function() {
+ expect(panels[0].title).not.toBeDefined();
});
- it("yet totally replacing the Object somewhere deeper doesn't reset the cache", function () {
- var immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 1
- }
- })
- },
- 'key2': {
- '@class': fields.frozendict.extend({}, {
- 'key3': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'row': 1
- }
- })
- },
- 'key4': {
- '@class': fields.string
- },
- '@meta': {
- 'row': 1
- }
- })
- }
- }).create({
- 'key1': 'string1',
- 'key2': {'key3': 'string3', 'key4': 'string4'}
- }),
- rows1 = extractRows(immutableObj),
- rows2;
+ it('simple title is defined for panels derived from ImmutableObject member', function() {
+ expect(panels[1].title).toEqual('container2');
+ });
- immutableObj.get('key2').set('key3', 'string5');
- rows2 = extractRows(immutableObj);
+ describe('getter/setter title is defined for panels derived from MutableObject members', function() {
+ it('no-args title invocation returns member ID', function() {
+ expect(panels[2].title()).toEqual('container1');
+ });
- expect(rows2).toBe(rows1);
+ it('title invocation with args sets new ID for a member', function() {
+ panels[2].title('newTitle');
+
+ expect(panels[2].title()).toEqual('newTitle');
+ });
})
+
});
+
+ describe('panel removal', function() {
+ it('removable flag is set only for panels derived from MutableObject members', function() {
+ expect(panels[0].removable).toBe(false);
+ expect(panels[1].removable).toBe(false);
+ expect(panels[2].removable).toBe(true);
+ });
+
+ it('invoking .remove() for removable panel actually removes it', function() {
+ panels[2].remove();
+ panels = extractPanels(obj, extractor);
+
+ expect(panels.length).toBe(2);
+ });
+
+ it('invoking .remove() for not removable panel does nothing', function() {
+ panels[0].remove();
+ panels[1].remove();
+ panels = extractPanels(obj, extractor);
+
+ expect(panels.length).toBe(3);
+ });
+ });
+
});
});
- describe('extractItems() behavior:', function() {
- var extractPanels, extractRows, extractItems, immutableObj;
-
+ describe('extractFields() behavior:', function() {
+ var extractFields, obj;
beforeEach(function() {
- extractPanels = $filter('extractPanels');
- extractRows = $filter('extractRows');
- extractItems = $filter('extractItems');
- immutableObj = fields.frozendict.extend({}, {
- 'key1': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 0,
- 'row': 0,
- 'index': 0
+ extractFields = $filter('extractFields');
+ obj = {
+ each: function(callback) {
+ var key;
+ for (key in this) {
+ if (this.hasOwnProperty(key) && key !== 'each') {
+ callback(key, this[key]);
}
- })
- },
- 'key2': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 0,
- 'row': 0,
- 'index': 1
- }
- })
- },
- 'key3': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 0,
- 'row': 0
- }
- })
- },
- 'key4': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 0,
- 'index': 0
- }
- })
- },
- 'key5': {
- '@class': fields.string.extend({}, {
- '@meta': {
- 'panelIndex': 0,
- 'index': 1
- }
- })
+ }
}
- }).create({
- 'key1': 'string1', 'key2': 'string2', 'key3': 'string3',
- 'key4': 'string4', 'key5': 'string5'
- });
+ }
});
- it('the filter is meant to be chainable only with extractRows() results', function() {
- var panels = extractPanels(immutableObj),
- rows = extractRows(panels[0]),
- items = extractItems(rows[0]);
-
- expect(items.length).toBe(3);
- });
-
- describe('the filter relies upon `@meta` object with `index` key', function() {
- describe('fields are ordered by the `index` key ascension, this applies', function() {
- it('to the fields with `row` key defined (ordering within a row)', function() {
- var panels = extractPanels(immutableObj),
- rows = extractRows(panels[0]),
- items = extractItems(rows[0]);
-
- expect(items[0].get()).toBe('string1');
- expect(items[1].get()).toBe('string2');
- });
-
- it('to the fields w/o `row` key defined (ordering of anonymous rows)', function() {
- var panels = extractPanels(immutableObj),
- rows = extractRows(panels[0]),
- items = extractItems(rows[1]);
-
- expect(items[0].get()).toBe('string4');
- expect(items[1].get()).toBe('string5');
- });
+ describe('basic expectations', function() {
+ var value1 = {value: 'some', uid: function() { return 1; }};
+ var value2 = {value: 'more', uid: function() { return 2; }};
+ function wrongCall() {
+ var wrongObj = {
+ key1: value1,
+ key2: value2
+ };
+ return extractFields(wrongObj);
+ }
+ function properCall() {
+ return extractFields(obj);
+ }
+ beforeEach(function() {
+ obj.key1 = value1;
+ obj.key2 = value2;
});
+ it('consumes any object implementing .each() method', function() {
+ expect(wrongCall).toThrow();
+ expect(properCall).not.toThrow();
+ });
+
+ it('produces plain JS object', function() {
+ var collectedFields = properCall();
+
+ expect(collectedFields).toEqual({key1: value1, 'key2': value2});
+ })
});
+ // TODO: describe caching behavior as soon as fields sorting is added to extractFields
+
});
+
+ describe('chunks() behavior:', function() {
+ // TODO: describe chunks behavior as soon as its responsibilities are fleshed out
+ // (inline propagation? other features? naming?)
+ });
+
});
\ No newline at end of file
diff --git a/merlin/test/js/merlin.models.spec.js b/merlin/test/js/merlin.models.spec.js
index db120c5..12a0043 100644
--- a/merlin/test/js/merlin.models.spec.js
+++ b/merlin/test/js/merlin.models.spec.js
@@ -47,41 +47,18 @@ describe('merlin models:', function() {
return value;
}
- function getCacheIDs() {
- return dictObj.getValues().map(function(item) {
- return item.getID();
- });
- }
-
- describe('getValues() method', function() {
- it('caching works from the very beginning', function() {
- expect(getCacheIDs()).toEqual(['id1', 'id2']);
- });
-
- it('keyValue() getter/setter can be used from the start', function() {
- var value = getValueFromCache('id1');
-
- expect(value.keyValue()).toBe('id1');
-
- value.keyValue('id3');
- expect(value.keyValue()).toBe('id3');
- expect(dictObj.getByID('id3')).toBeDefined();
- });
- });
-
describe('add() method', function() {
it('adds an empty value with given key', function() {
dictObj.add('id3');
expect(dictObj.getByID('id3').get()).toBe('');
- expect(getCacheIDs()).toEqual(['id1', 'id2', 'id3']);
});
it('keyValue() getter/setter can be used for added values', function() {
var value;
dictObj.add('id3');
- value = getValueFromCache('id3');
+ value = dictObj.getByID('id3');
expect(value.keyValue()).toBe('id3');
@@ -112,31 +89,28 @@ describe('merlin models:', function() {
});
describe('empty() method', function() {
- it('removes all entries in model and in cache', function() {
+ it('removes all entries in model', function() {
dictObj.empty();
expect(dictObj.getIDs().length).toBe(0);
- expect(dictObj.getValues().length).toBe(0);
})
});
describe('resetKeys() method', function() {
- it('re-sets dictionary contents to given keys, cache included', function() {
+ it('re-sets dictionary contents to given keys', function() {
dictObj.resetKeys(['key1', 'key2']);
expect(dictObj.getIDs()).toEqual(['key1', 'key2']);
expect(dictObj.getByID('key1').get()).toBe('');
expect(dictObj.getByID('key2').get()).toBe('');
- expect(getCacheIDs()).toEqual(['key1', 'key2']);
})
});
describe('removeItem() method', function() {
- it('removes dictionary entry by key from model and cache', function() {
+ it('removes dictionary entry by key from model', function() {
dictObj.removeItem('id1');
expect(dictObj.getByID('id1')).toBeUndefined();
- expect(getCacheIDs()).toEqual(['id2']);
})
});