<commit_before>"use strict";

describe("relation", function() {
  var mockConfigurator = {
    configurationFor: function(name) {
      return {};
    },
    getRetrievers: function(name) {
      return {};
    },
    getConfAndDelegate: function(name, obj) {
      var relations = {
        labels: {
          PRED : { short: "PRED", long: "predicate" },
          OBJ : { short: "OBJ", long: "object" },
          COORD : { short: "COORD", long: "coordination" }
        }
      };
      obj.conf = { relations: relations };
    }
  };

  var createTokens = function() {
    return {
      '01': {
        id: '01',
        string: 'Arma',
        relation: {
          label: "OBJ_CO"
        }
      },
      '02': {
        id: '02',
        string: 'virum',
        relation: {
          label: "OBJ_CO"
        }
      },
      '03': {
        id: '03',
        string: '-que',
        relation: {
          label: "COORD"
        }
      },
      '04': {
        id: '04',
        string: 'cano',
        relation: {
          label: "PRED"
        }
      }
    };
  };

  beforeEach(module("arethusa.core", function($provide) {
    $provide.value('configurator', mockConfigurator);
  }));

  beforeEach(module("arethusa.relation"));

  var relation;
  var state;
  beforeEach(inject(function(_relation_, _state_) {
    state = _state_;
    state.tokens = createTokens();
    relation = _relation_;
    relation.init();
  }));

  describe('label handling', function() {
    describe('this.buildLabel', function() {
      describe('build a label from prefix and suffix values', function () {
        it('combines a prefix and a suffix label to one, joined by an underscore', function() {
          var relationLabel = { prefix: "PRED", suffix: "CO" };
          relation.buildLabel(relationLabel);
          expect(relationLabel.label).toEqual("PRED_CO");
        });
      });

      it("doesn't add unnecessary underscores", function() {
        var relationLabel = { prefix: "ATR" };
        relation.buildLabel(relationLabel);
        expect(relationLabel.label).toEqual("ATR");
      });
    });

    describe('this.expandRelation', function() {
      describe('with two labels', function () {
        it('splits a label like PRED_CO into PRED and CO', function() {
          var relationLabel = { label: "PRED_CO" };
          relation.expandRelation(relationLabel);
          expect(relationLabel.prefix).toEqual("PRED");
          expect(relationLabel.suffix).toEqual("CO");
        });
      });

      describe('with one label', function () {
        it('does not affect single labels like PRED', function() {
          var relationLabel = { label: "PRED" };
          relation.expandRelation(relationLabel);
          expect(relationLabel.prefix).toEqual("PRED");
          expect(relationLabel.suffix).toBeUndefined();
        });
      });
    });
  });

  describe('multi changes', function() {
    describe('this.multiChangePossible', function() {
      it('is false when there is no selected token', function() {
        expect(relation.multiChangePossible()).toBeFalsy();
      });

      it('needs a valid multiChanger model', function() {
        state.selectToken('01', 'click');
        expect(relation.multiChangePossible()).toBeFalsy();

        relation.multiChanger = { prefix: "ATR" };
        expect(relation.multiChangePossible()).toBeTruthy();
      });
    });

    describe('this.applyMultiChanger', function() {
      it('changes the relation value of all selected tokens', function() {
        state.selectToken('01', 'ctrl-click');
        state.selectToken('02', 'ctrl-click');
        var newRel = { label: "ATR" };
        relation.multiChanger = newRel;
        relation.applyMultiChanger();

        expect(state.tokens['01'].relation.label).toEqual("ATR");
        expect(state.tokens['02'].relation.label).toEqual("ATR");
      });

      it('passes on unique relation objects', function() {
        state.selectToken('01', 'ctrl-click');
        state.selectToken('02', 'ctrl-click');
        var newRel = { label: "ATR" };
        relation.multiChanger = newRel;
        relation.applyMultiChanger();

        var rel1 = state.tokens['01'].relation;
        var rel2 = state.tokens['02'].relation;
        expect(rel1).not.toBe(rel2);
      });
    });
  });

  describe('working with selections', function() {
    describe('this.selectLabel', function() {
      it('selects tokens by label', function() {
        relation.selectByLabel('OBJ_CO');
        expect(state.isSelected('01')).toBeTruthy();
        expect(state.isSelected('02')).toBeTruthy();
        expect(state.isSelected('03')).toBeFalsy();
        expect(state.isSelected('04')).toBeFalsy();

        relation.selectByLabel('COORD');
        expect(state.isSelected('01')).toBeFalsy();
        expect(state.isSelected('02')).toBeFalsy();
        expect(state.isSelected('03')).toBeTruthy();
        expect(state.isSelected('04')).toBeFalsy();
      });
    });

    describe('this.currentLabels', function() {
      it('returns obj of all selected tokens with their id, string and relation obj', function() {
        state.selectToken('01', 'ctrl-click');
        state.selectToken('02', 'ctrl-click');
        var res = relation.currentLabels();

        var rel1 = { string: 'Arma',  relation: state.tokens['01'].relation };
        var rel2 = { string: 'virum', relation: state.tokens['02'].relation };

        expect(res['01']).toEqual(rel1);
        expect(res['02']).toEqual(rel2);
        expect(res['03']).toBeUndefined();
        expect(res['04']).toBeUndefined();
      });
    });
  });

  describe('this.resetAncestors', function() {
    it('takes a rel object and clears out its ancestors', function() {
      var relObj = { ancestors: ['1', '2', '3'] };
      relation.resetAncestors(relObj);
      expect(relObj.ancestors).toEqual([]);
    });
  });

  describe('init functions', function() {
    describe('this.createInternalState', function() {
      it('sets an internal state, which is a selection of the tokens string and relation obj', function() {
        var res = relation.createInternalState();

        var rel1 = { string: 'Arma',  relation: state.tokens['01'].relation };
        var rel2 = { string: 'virum', relation: state.tokens['02'].relation };
        var rel3 = { string: '-que',  relation: state.tokens['03'].relation };
        var rel4 = { string: 'cano',  relation: state.tokens['04'].relation };

        expect(res['01']).toEqual(rel1);
        expect(res['02']).toEqual(rel2);
        expect(res['03']).toEqual(rel3);
        expect(res['04']).toEqual(rel4);
      });
    });
  });
});
<commit_msg>Add specs for relation.buildLabelAndSearch()<commit_after>"use strict";

describe("relation", function() {
  var mockConfigurator = {
    configurationFor: function(name) {
      return {};
    },
    getRetrievers: function(name) {
      return {};
    },
    getConfAndDelegate: function(name, obj) {
      var relations = {
        labels: {
          PRED : { short: "PRED", long: "predicate" },
          OBJ : { short: "OBJ", long: "object" },
          COORD : { short: "COORD", long: "coordination" }
        }
      };
      obj.conf = { relations: relations };
    }
  };

  var createTokens = function() {
    return {
      '01': {
        id: '01',
        string: 'Arma',
        relation: {
          label: "OBJ_CO"
        }
      },
      '02': {
        id: '02',
        string: 'virum',
        relation: {
          label: "OBJ_CO"
        }
      },
      '03': {
        id: '03',
        string: '-que',
        relation: {
          label: "COORD"
        }
      },
      '04': {
        id: '04',
        string: 'cano',
        relation: {
          label: "PRED"
        }
      }
    };
  };

  beforeEach(module("arethusa.core", function($provide) {
    $provide.value('configurator', mockConfigurator);
  }));

  beforeEach(module("arethusa.relation"));

  var relation;
  var state;
  beforeEach(inject(function(_relation_, _state_) {
    state = _state_;
    state.tokens = createTokens();
    relation = _relation_;
    relation.init();
  }));

  describe('label handling', function() {
    describe('this.buildLabel', function() {
      describe('build a label from prefix and suffix values', function () {
        it('combines a prefix and a suffix label to one, joined by an underscore', function() {
          var relationLabel = { prefix: "PRED", suffix: "CO" };
          relation.buildLabel(relationLabel);
          expect(relationLabel.label).toEqual("PRED_CO");
        });
      });

      it("doesn't add unnecessary underscores", function() {
        var relationLabel = { prefix: "ATR" };
        relation.buildLabel(relationLabel);
        expect(relationLabel.label).toEqual("ATR");
      });
    });

    describe('this.expandRelation', function() {
      describe('with two labels', function () {
        it('splits a label like PRED_CO into PRED and CO', function() {
          var relationLabel = { label: "PRED_CO" };
          relation.expandRelation(relationLabel);
          expect(relationLabel.prefix).toEqual("PRED");
          expect(relationLabel.suffix).toEqual("CO");
        });
      });

      describe('with one label', function () {
        it('does not affect single labels like PRED', function() {
          var relationLabel = { label: "PRED" };
          relation.expandRelation(relationLabel);
          expect(relationLabel.prefix).toEqual("PRED");
          expect(relationLabel.suffix).toBeUndefined();
        });
      });
    });
  });

  describe('multi changes', function() {
    describe('this.multiChangePossible', function() {
      it('is false when there is no selected token', function() {
        expect(relation.multiChangePossible()).toBeFalsy();
      });

      it('needs a valid multiChanger model', function() {
        state.selectToken('01', 'click');
        expect(relation.multiChangePossible()).toBeFalsy();

        relation.multiChanger = { prefix: "ATR" };
        expect(relation.multiChangePossible()).toBeTruthy();
      });
    });

    describe('this.applyMultiChanger', function() {
      it('changes the relation value of all selected tokens', function() {
        state.selectToken('01', 'ctrl-click');
        state.selectToken('02', 'ctrl-click');
        var newRel = { label: "ATR" };
        relation.multiChanger = newRel;
        relation.applyMultiChanger();

        expect(state.tokens['01'].relation.label).toEqual("ATR");
        expect(state.tokens['02'].relation.label).toEqual("ATR");
      });

      it('passes on unique relation objects', function() {
        state.selectToken('01', 'ctrl-click');
        state.selectToken('02', 'ctrl-click');
        var newRel = { label: "ATR" };
        relation.multiChanger = newRel;
        relation.applyMultiChanger();

        var rel1 = state.tokens['01'].relation;
        var rel2 = state.tokens['02'].relation;
        expect(rel1).not.toBe(rel2);
      });
    });
  });

  describe('working with selections', function() {
    describe('this.selectLabel', function() {
      it('selects tokens by label', function() {
        relation.selectByLabel('OBJ_CO');
        expect(state.isSelected('01')).toBeTruthy();
        expect(state.isSelected('02')).toBeTruthy();
        expect(state.isSelected('03')).toBeFalsy();
        expect(state.isSelected('04')).toBeFalsy();

        relation.selectByLabel('COORD');
        expect(state.isSelected('01')).toBeFalsy();
        expect(state.isSelected('02')).toBeFalsy();
        expect(state.isSelected('03')).toBeTruthy();
        expect(state.isSelected('04')).toBeFalsy();
      });
    });

    describe('this.currentLabels', function() {
      it('returns obj of all selected tokens with their id, string and relation obj', function() {
        state.selectToken('01', 'ctrl-click');
        state.selectToken('02', 'ctrl-click');
        var res = relation.currentLabels();

        var rel1 = { string: 'Arma',  relation: state.tokens['01'].relation };
        var rel2 = { string: 'virum', relation: state.tokens['02'].relation };

        expect(res['01']).toEqual(rel1);
        expect(res['02']).toEqual(rel2);
        expect(res['03']).toBeUndefined();
        expect(res['04']).toBeUndefined();
      });
    });
  });

  describe('this.resetAncestors', function() {
    it('takes a rel object and clears out its ancestors', function() {
      var relObj = { ancestors: ['1', '2', '3'] };
      relation.resetAncestors(relObj);
      expect(relObj.ancestors).toEqual([]);
    });
  });

  describe('this.buildLabelAndSearch', function() {
    it('builds a label on an obj and searches for it afterwards', function() {
      var relObj = { prefix: 'OBJ', suffix: 'CO' };
      relation.buildLabelAndSearch(relObj);
      expect(state.isSelected('01')).toBeTruthy();
      expect(state.isSelected('02')).toBeTruthy();
    });

    it('when no obj is given, the searchedLabel model is used instead', function() {
      relation.searchedLabel = { prefix: 'OBJ', suffix: 'CO' };
      relation.buildLabelAndSearch();
      expect(state.isSelected('01')).toBeTruthy();
      expect(state.isSelected('02')).toBeTruthy();
    });
  });

  describe('init functions', function() {
    describe('this.createInternalState', function() {
      it('sets an internal state, which is a selection of the tokens string and relation obj', function() {
        var res = relation.createInternalState();

        var rel1 = { string: 'Arma',  relation: state.tokens['01'].relation };
        var rel2 = { string: 'virum', relation: state.tokens['02'].relation };
        var rel3 = { string: '-que',  relation: state.tokens['03'].relation };
        var rel4 = { string: 'cano',  relation: state.tokens['04'].relation };

        expect(res['01']).toEqual(rel1);
        expect(res['02']).toEqual(rel2);
        expect(res['03']).toEqual(rel3);
        expect(res['04']).toEqual(rel4);
      });
    });
  });
});
<|endoftext|>


<commit_before>/*
    #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
      This file is part of the Smart Developer Hub Project:
        http://www.smartdeveloperhub.org/
      Center for Open Middleware
            http://www.centeropenmiddleware.com/
    #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
      Copyright (C) 2015 Center for Open Middleware.
    #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
      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.
    #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
*/

(function() {
    var joint= null;

    var normalizeConfig = function normalizeConfig(configuration) {
        if (typeof configuration !== 'object') {
            configuration = {};
        }

        if (typeof configuration.label !== 'string') {
            configuration.label = "---";
        }

        if (typeof configuration.background !== 'string') {
            configuration.background = "#FFF";
        }

        if (typeof configuration.iconbackground !== 'string') {
            configuration.iconbackground = "#68B828";
        }

        if (typeof configuration.initfrom !== "number") {
            configuration.initfrom = 0;
        }

        if (typeof configuration.initto !== "number") {
            configuration.initto = 0;
        }

        if (typeof configuration.changetime !== "number") {
            configuration.changetime = 3;
        }

        if (typeof configuration.changeeasing !== "boolean") {
            configuration.changeeasing = true;
        }

        if (typeof configuration.label2 !== "string") {
            configuration.label2 = true;
        }

        if (typeof configuration.label2color !== "string") {
            configuration.label2color = "rgba(0, 0, 0, 0.7)";
        }

        if (typeof configuration.counter2color !== "string") {
            configuration.counter2color = "#000";
        }

        if (typeof configuration.decimal2 !== "number") {
            configuration.decimal2 = 2;
        }

        if (typeof configuration.icon !== "string") {
            configuration.icon = "octicon octicon-octoface";
        }

        if (typeof configuration.iconcolor !== "string") {
            configuration.iconcolor = "#FFF";
        }

        if (typeof configuration.labelcolor !== "string") {
            configuration.labelcolor = "rgba(0, 0, 0, 0.7)";
        }

        if (typeof configuration.countercolor !== "string") {
            configuration.countercolor = "#000";
        }

        if (typeof configuration.decimal !== "number") {
            configuration.decimal = 2;
        }
        return configuration;
    };

    /* BuildTransitions constructor
    *   element: the DOM element that will contains the BuildTransitions div
    *   data: the data id array
    *   contextId: if necesary, the contextId link this chart data data
    *           with changes in other context provider chart.
    *  configuration: you can use his optional parameter to assing a custom
    *       contextID for this context provider chart. Ej:
    *      {
    *         label: label text,
    *         labelcolor: label text color,
    *         countercolor: color of the number,
    *         label2: label2 text,
    *         label2color: label2 text color,
    *         counter2color: color of the second counter number,
    *         background: optional color in any css compatible format ej: "#0C0C0C" (default #FFF),
    *         initfrom: optional initial animation value (default 0),
    *         initto: optional final animation value (default 0),
    *         changetime: this time in seconds set the change data animation duration (default 3),
    *         changeeasing: optional animation effect data-easing fast-slow (default true),
    *         icon: optional icon css class (default "octicon octicon-octoface"),
    *         iconcolor: widget icon color,
    *         iconbackground: optional color in any css compatible format ej: "#0C0C0C" (default #68B828),
    *         decimal: number of decimals in metric value
    *      }
    */
    var BuildTransitions = function BuildTransitions(element, metrics, contextId, configuration) {
        console.log("BuildTransitions 1");
        if(!framework.isReady()) {
            console.error("BuildTransitions object could not be created because framework is not loaded.");
            return;
        }

        // CHECK Joint.js
        if(typeof joint === 'undefined') {
            console.log("BuildTransitions could not be loaded.");
            //return;
        }

        // extending widget
        this.element = $(element);
        framework.widgets.CommonWidget.call(this, false, this.element.get(0));

        // Configuration
        //this.configuration = normalizeConfig(configuration);
        this.configuration = configuration;

        this.observeCallback = this.commonObserveCallback.bind(this);

        framework.data.observe(metrics, function(e) {
                this.observeCallback(e);
            }.bind(this),
            contextId, 1
        );

    };

    BuildTransitions.prototype = new framework.widgets.CommonWidget(true);

    BuildTransitions.prototype.updateData = function(framework_data) {
        console.log("Updating BuildTransitions 1");
        var normalizedData = getNormalizedData.call(this,framework_data);

        //Update data
        if(this.chart != null) {
            updateValues.call(this, normalizedData);
        } else { // Paint it for first time
            paint.call(this, normalizedData, framework_data);
        }
    };

    BuildTransitions.prototype.delete = function() {

        //Stop observing for data changes
        framework.data.stopObserve(this.observeCallback);

        //Clear DOM
        $(this.container).empty();
        this.element.empty();

    };

    //Function that returns the value to replace with the label variables
    var replacer = function(resourceId, resource, str) {

        //Remove the initial an trailing '%' of the string
        str = str.substring(1, str.length-1);

        //Check if it is a parameter an return its value
        if(str === "resourceId") { //Special command to indicate the name of the resource
            return resourceId;

        } else { // Obtain its value through the object given the path

            var path = str.split(".");
            var subObject = resource;

            for(var p = 0; p < path.length; ++p) {
                if((subObject = subObject[path[p]]) == null)
                    return "";
            }

            return subObject.toString();
        }

    };
    var currentValues = {
            "pas_pas": 0,
            "pas_brk": 0,
            "brk_fix": 0,
            "fix_pas": 0,
            "fix_fai": 0,
            "fai_fai": 0,
            "fai_fix": 0
    };
    // TODO take this from config
    var flagMat = {
            paspasmetricid: "pas_pas",
            pasbrkmetricid: "pas_brk",
            brkfixmetricid: "brk_fix",
            fixpasmetricid: "fix_pas",
            fixfaimetricid: "fix_fai",
            faifaimetricid: "fai_fai",
            faifixmetricid: "fai_fix"
    };

    /**
     * random number from interval.
     * @param min
     * @param max
     * @returns {Number} random number
     */
    var randomIntFromInterval = function randomIntFromInterval(min,max) {
        return Math.floor(Math.random()*(max-min+1)+min);
    };

    /**
     * Update link values.
     * @param data
     * @returns {Array} Contains objects with 'label' and 'value'.
     */
    var updateValues = function updateValues(framework_data) {
        //TODO use real data
        /*for (var metricId in framework_data) {
            for (var m in framework_data[metricId]) {
                // only 1
                currentValues[flagMat[metricId]] = framework_data[metricId][m]['data'][0];
            }
        }*/
        currentValues = {
            "pas_pas": randomIntFromInterval(1,300),
            "pas_brk": randomIntFromInterval(1,300),
            "brk_fix": randomIntFromInterval(1,300),
            "brk_fai": randomIntFromInterval(1,300),
            "fix_pas": randomIntFromInterval(1,300),
            "fix_brk": randomIntFromInterval(1,300),
            "fai_fai": randomIntFromInterval(1,300),
            "fai_fix": randomIntFromInterval(1,300)
        };
        for (var key in this.linksById) {
            this.linksById[key].label(0, {
                position: .5,
                attrs: {
                    text: { text: currentValues[key] }
                }
            })
        }
    };

    /**
     * Gets a normalized array of data according to the chart expected input from the data returned by the framework.
     * @param framework_data
     * @returns {Array} Contains objects with 'label' and 'value'.
     */
    var getNormalizedData = function getNormalizedData(framework_data) {
        return framework_data;
    };

    /**
     * Gets a normalized array of data according to the chart expected input from the data returned by the framework.
     * @param framework_data
     * @returns {Array} Contains objects with 'label' and 'value'.
     */
    var paint = function paint(normalizedData, framework_data) {
        //require(['joint'], function(joint) {
        var graph = new joint.dia.Graph();
        var paper = new joint.dia.Paper({
            el: this.element,
            width: 630,
            height: 348,
            gridSize: 10,
            perpendicularLinks: true,
            model: graph
        });

        var pn = joint.shapes.pn;

        // Generic box
        var genericBox = new pn.Transition({
            size: { width: 70, height: 70 },
            attrs: {
                '.label': { fill: 'black'},
                '.root' : { fill: '#9586fd', stroke: '#9586fd' }
            }
        });

        //Generic Ball
        var genericBall = new pn.Place({
            position: { x: 200, y: 50 },
            size: { width: 70, height: 70 },
            attrs: {
                '.root' : { stroke: '#9586fd', 'stroke-width': 3},
                '.tokens > circle': { fill : '#7a7e9b'}
            }
        });

        var passedBall = genericBall.clone().attr({
            '.alot > text': {
                'fill': 'green',
                'font-size': 12
            }
        }).position(60, 60).set('tokens', "Passed");

        var brokenBall = genericBall.clone().attr({
            '.alot > text': {
                'fill': 'red',
                'font-size': 12
            }
        }).position(60, 225).set('tokens', 'Broken');

        var fixedBall = genericBall.clone().attr({
            '.alot > text': {
                'fill': 'green',
                'font-size': 12
            }
        }).position(400, 60).set('tokens', 'Fixed');

        var failedBall = genericBall.clone().attr({
            '.alot > text': {
                'fill': 'red',
                'font-size': 12
            }
        }).position(400, 225).set('tokens', 'Failed');

        this.linksById = {};

        var link = function link (a, b, type, linkShape, color, position) {
            if (typeof position !== 'number') {
                position = .2;
            } else {
                console.log(position);
            }
            var newLink = new pn.Link({
                source: { id: a.id, selector: '.root'},
                target: { id: b.id, selector: '.root' },
                attrs: {
                    '.connection': {
                        'fill': 'none',
                        'stroke-linejoin': 'round',
                        'stroke-width': '2',
                        'stroke': '#4b4a67'
                    }
                },
                labels: [
                    {
                        position: position,
                        attrs: {
                            rect: { fill: 'white' },
                            text: {
                                'fill': color,
                                'text': "--",
                                'font-family': 'Courier New',
                                'font-size': 20,
                                'font-weight': 'bold',
                                'ref-x': 0.5,
                                'ref-y': 0.5
                            }
                        }
                    }
                ]
            });
            /*newLink.label(0, {
                position: position,
                attrs: {
                    rect: { fill: 'white' },
                    text: {
                        'fill': color,
                        'text': "--",
                        'font-family': 'Courier New',
                        'font-size': 20,
                        'font-weight': 'bold',
                        'ref-x': 0.5,
                        'ref-y': 0.5
                    }
                }
            });*/
            //newLink.set('manhattan', true);
            //newLink.set('smooth', true);
            //newLink.set('orthogonal', true);
            if (typeof linkShape !== 'object' || linkShape == null) {
                newLink.set("smooth", true);
            } else {
                for (var i = linkShape.length - 1; i >= 0; i--) {
                    newLink.set(linkShape[i], true);
                };
            }
            this.linksById[type] = newLink;

            // Ñapping edges
            if (type == "brk_fix") {
                newLink.set("vertices", [{ x: 255, y: 155 }]);
            } else if (type == "fix_brk") {
                newLink.set("vertices", [{ x: 280, y: 200 }]);
            } else if (type == "fai_fai") {
                newLink.set("vertices", [{ x: 502, y: 317 }]);
            }

            return newLink;
        }.bind(this);

        graph.addCell([ passedBall, brokenBall, fixedBall, failedBall]);

        graph.addCell([
            link(passedBall, passedBall, "pas_pas", ["manhattan", "smooth"], 'green'),
            link(passedBall, brokenBall, "pas_brk", null, 'red'),
            link(brokenBall, fixedBall, "brk_fix", null, 'green'),
            link(brokenBall, failedBall, "brk_fai", null, 'red'),
            link(fixedBall, passedBall, "fix_pas", null, 'green'),
            link(fixedBall, brokenBall, "fix_brk", ["smooth"], 'red', 0),
            link(failedBall, failedBall, "fai_fai", ["manhattan", "smooth"], 'red'),
            link(failedBall, fixedBall, "fai_fix", null, 'green', -0.1)
        ]);

        function fireTransition(t, sec) {

            var allbound = graph.getConnectedLinks(t);
            var elemcolors = [];
            var color;
            for (var i = 0; i < allbound.length; i++) {
                var source = graph.getCell(allbound[i].get('source').id);
                var target = graph.getCell(allbound[i].get('target').id);
                var sourceid = source.attributes.tokens;
                var targetid = target.attributes.tokens;
                if (targetid == "Failed" || targetid == "Broken") {
                    color = "red";
                } else {
                    color = "green";
                }
                elemcolors.push({target : target, source : source, color: color});
            }

            var isFirable = true;

            if (isFirable) {
                _.each(elemcolors,
                    function(p) {
                        var link = _.find(allbound, function(l) {
                        return (l.get('target').id === p.target.id) && (l.get('source').id === p.source.id)
                    });
                    paper.findViewByModel(link).sendToken(joint.Vectorizer('circle', { r: 7, fill: p.color }).node, sec * 1300);
                });
            }
        }

        function simulate() {
            var transitions = [passedBall, failedBall, brokenBall, fixedBall];
            _.each(transitions, function(t) { fireTransition(t, 1); });
            return setInterval(function() {
                _.each(transitions, function(t) { fireTransition(t, 1); });
            }, 2000);
        }

        function stopSimulation(simulationId) {
            clearInterval(simulationId);
        }

        var simulationId = simulate();

        updateValues.call(this, normalizedData);
        //}).bind(this);
    };

    window.framework.widgets.BuildTransitions = BuildTransitions;

    // AMD compliant
    if ( typeof define === "function" && define.amd) {
        define( ['joint'], function (_joint) {
            joint = _joint;
            return BuildTransitions;
        });
    }

})();<commit_msg>[Widget][buildTransitions] Add Tooltips. First step<commit_after>/*
    #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
      This file is part of the Smart Developer Hub Project:
        http://www.smartdeveloperhub.org/
      Center for Open Middleware
            http://www.centeropenmiddleware.com/
    #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
      Copyright (C) 2015 Center for Open Middleware.
    #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
      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.
    #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
*/

(function() {
    var joint= null;

    var normalizeConfig = function normalizeConfig(configuration) {
        if (typeof configuration !== 'object') {
            configuration = {};
        }

        if (typeof configuration.label !== 'string') {
            configuration.label = "---";
        }

        if (typeof configuration.background !== 'string') {
            configuration.background = "#FFF";
        }

        if (typeof configuration.iconbackground !== 'string') {
            configuration.iconbackground = "#68B828";
        }

        if (typeof configuration.initfrom !== "number") {
            configuration.initfrom = 0;
        }

        if (typeof configuration.initto !== "number") {
            configuration.initto = 0;
        }

        if (typeof configuration.changetime !== "number") {
            configuration.changetime = 3;
        }

        if (typeof configuration.changeeasing !== "boolean") {
            configuration.changeeasing = true;
        }

        if (typeof configuration.label2 !== "string") {
            configuration.label2 = true;
        }

        if (typeof configuration.label2color !== "string") {
            configuration.label2color = "rgba(0, 0, 0, 0.7)";
        }

        if (typeof configuration.counter2color !== "string") {
            configuration.counter2color = "#000";
        }

        if (typeof configuration.decimal2 !== "number") {
            configuration.decimal2 = 2;
        }

        if (typeof configuration.icon !== "string") {
            configuration.icon = "octicon octicon-octoface";
        }

        if (typeof configuration.iconcolor !== "string") {
            configuration.iconcolor = "#FFF";
        }

        if (typeof configuration.labelcolor !== "string") {
            configuration.labelcolor = "rgba(0, 0, 0, 0.7)";
        }

        if (typeof configuration.countercolor !== "string") {
            configuration.countercolor = "#000";
        }

        if (typeof configuration.decimal !== "number") {
            configuration.decimal = 2;
        }
        return configuration;
    };

    /* BuildTransitions constructor
    *   element: the DOM element that will contains the BuildTransitions div
    *   data: the data id array
    *   contextId: if necesary, the contextId link this chart data data
    *           with changes in other context provider chart.
    *  configuration: you can use his optional parameter to assing a custom
    *       contextID for this context provider chart. Ej:
    *      {
    *         label: label text,
    *         labelcolor: label text color,
    *         countercolor: color of the number,
    *         label2: label2 text,
    *         label2color: label2 text color,
    *         counter2color: color of the second counter number,
    *         background: optional color in any css compatible format ej: "#0C0C0C" (default #FFF),
    *         initfrom: optional initial animation value (default 0),
    *         initto: optional final animation value (default 0),
    *         changetime: this time in seconds set the change data animation duration (default 3),
    *         changeeasing: optional animation effect data-easing fast-slow (default true),
    *         icon: optional icon css class (default "octicon octicon-octoface"),
    *         iconcolor: widget icon color,
    *         iconbackground: optional color in any css compatible format ej: "#0C0C0C" (default #68B828),
    *         decimal: number of decimals in metric value
    *      }
    */
    var BuildTransitions = function BuildTransitions(element, metrics, contextId, configuration) {
        console.log("BuildTransitions 1");
        if(!framework.isReady()) {
            console.error("BuildTransitions object could not be created because framework is not loaded.");
            return;
        }

        // CHECK Joint.js
        if(typeof joint === 'undefined') {
            console.log("BuildTransitions could not be loaded.");
            //return;
        }

        // extending widget
        this.element = $(element);
        framework.widgets.CommonWidget.call(this, false, this.element.get(0));

        // Configuration
        //this.configuration = normalizeConfig(configuration);
        this.configuration = configuration;

        this.observeCallback = this.commonObserveCallback.bind(this);

        framework.data.observe(metrics, function(e) {
                this.observeCallback(e);
            }.bind(this),
            contextId, 1
        );

    };

    BuildTransitions.prototype = new framework.widgets.CommonWidget(true);

    BuildTransitions.prototype.updateData = function(framework_data) {
        console.log("Updating BuildTransitions 1");
        var normalizedData = getNormalizedData.call(this,framework_data);

        //Update data
        if(this.chart != null) {
            updateValues.call(this, normalizedData);
        } else { // Paint it for first time
            paint.call(this, normalizedData, framework_data);
        }
    };

    BuildTransitions.prototype.delete = function() {

        //Stop observing for data changes
        framework.data.stopObserve(this.observeCallback);

        //Clear DOM
        $(this.container).empty();
        this.element.empty();

    };

    var currentValues = {
            "pas_pas": 0,
            "pas_brk": 0,
            "brk_fix": 0,
            "fix_pas": 0,
            "fix_fai": 0,
            "fai_fai": 0,
            "fai_fix": 0
    };
    // TODO take this from config
    var flagMat = {
            paspasmetricid: "pas_pas",
            pasbrkmetricid: "pas_brk",
            brkfixmetricid: "brk_fix",
            fixpasmetricid: "fix_pas",
            fixfaimetricid: "fix_fai",
            faifaimetricid: "fai_fai",
            faifixmetricid: "fai_fix"
    };

    /**
     * random number from interval.
     * @param min
     * @param max
     * @returns {Number} random number
     */
    var randomIntFromInterval = function randomIntFromInterval(min,max) {
        return Math.floor(Math.random()*(max-min+1)+min);
    };

    /**
     * Update link values.
     * @param data
     * @returns {Array} Contains objects with 'label' and 'value'.
     */
    var updateValues = function updateValues(framework_data) {
        //TODO use real data
        /*for (var metricId in framework_data) {
            for (var m in framework_data[metricId]) {
                // only 1
                currentValues[flagMat[metricId]] = framework_data[metricId][m]['data'][0];
            }
        }*/
        currentValues = {
            "pas_pas": randomIntFromInterval(1000,10000),
            "pas_brk": randomIntFromInterval(1000,10000),
            "brk_fix": randomIntFromInterval(1000,10000),
            "brk_fai": randomIntFromInterval(1000,10000),
            "fix_pas": randomIntFromInterval(1000,10000),
            "fix_brk": randomIntFromInterval(1000,10000),
            "fai_fai": randomIntFromInterval(1000,10000),
            "fai_fix": randomIntFromInterval(1000,10000)
        };
        for (var key in this.linksById) {
            this.linksById[key].label(0, {
                position: .5,
                attrs: {
                    text: { text: currentValues[key] }
                }
            })
        }
        // Update Tooltips
        var allLabels = this.element.find('.link').find('.label').find('tspan');
        allLabels.qtip(
            {
                id: 'sampletooltip',
                content: {
                    text: 'Hi. I am a sample tooltip!',
                    title: 'Sample tooltip'
                },
                show: {
                    event: 'mouseenter'
                },
                hide: {
                    event: 'mouseleave unfocus'
                },
                position: {
                    my: 'top center',
                    at: 'bottom center'
                },
                style: {
                    classes: 'qtip-bootstrap',
                    tip: {
                        width: 16,
                        height: 6
                    }
                }
            }
        );
    };

    /**
     * Gets a normalized array of data according to the chart expected input from the data returned by the framework.
     * @param framework_data
     * @returns {Array} Contains objects with 'label' and 'value'.
     */
    var getNormalizedData = function getNormalizedData(framework_data) {
        return framework_data;
    };

    /**
     * Gets a normalized array of data according to the chart expected input from the data returned by the framework.
     * @param framework_data
     * @returns {Array} Contains objects with 'label' and 'value'.
     */
    var paint = function paint(normalizedData, framework_data) {
        //require(['joint'], function(joint) {
        var graph = new joint.dia.Graph();

        var paper = new joint.dia.Paper({
            el: this.element,
            width: 630,
            height: 348,
            gridSize: 10,
            perpendicularLinks: true,
            model: graph,
            interactive: false
        });

        var pn = joint.shapes.pn;

        // Generic box
        var genericBox = new pn.Transition({
            size: { width: 70, height: 70 },
            attrs: {
                '.label': { fill: 'black'},
                '.root' : { fill: '#9586fd', stroke: '#9586fd' }
            }
        });

        //Generic Ball
        var genericBall = new pn.Place({
            position: { x: 200, y: 50 },
            size: { width: 70, height: 70 },
            attrs: {
                '.root' : { stroke: '#9586fd', 'stroke-width': 3},
                '.tokens > circle': { fill : '#7a7e9b'}
            }
        });

        var passedBall = genericBall.clone().attr({
            '.alot > text': {
                'fill': 'green',
                'font-size': 12
            }
        }).position(60, 60).set('tokens', "Passed");

        var brokenBall = genericBall.clone().attr({
            '.alot > text': {
                'fill': 'red',
                'font-size': 12
            }
        }).position(60, 225).set('tokens', 'Broken');

        var fixedBall = genericBall.clone().attr({
            '.alot > text': {
                'fill': 'green',
                'font-size': 12
            }
        }).position(400, 60).set('tokens', 'Fixed');

        var failedBall = genericBall.clone().attr({
            '.alot > text': {
                'fill': 'red',
                'font-size': 12
            }
        }).position(400, 225).set('tokens', 'Failed');

        this.linksById = {};

        var link = function link (a, b, type, linkShape, color, position) {
            if (typeof position !== 'number') {
                position = .2;
            } else {
                console.log(position);
            }
            var newLink = new pn.Link({
                source: { id: a.id, selector: '.root'},
                target: { id: b.id, selector: '.root' },
                attrs: {
                    '.connection': {
                        'fill': 'none',
                        'stroke-linejoin': 'round',
                        'stroke-width': '2',
                        'stroke': '#4b4a67'
                    }
                },
                labels: [
                    {
                        position: position,
                        attrs: {
                            rect: { fill: 'white' },
                            text: {
                                'fill': color,
                                'text': "--",
                                'font-family': 'Courier New',
                                'font-size': 20,
                                'font-weight': 'bold',
                                'ref-x': 0.5,
                                'ref-y': 0.5
                            }
                        }
                    }
                ]
            });
            //newLink.set('manhattan', true);
            //newLink.set('smooth', true);
            //newLink.set('orthogonal', true);
            if (typeof linkShape !== 'object' || linkShape == null) {
                newLink.set("smooth", true);
            } else {
                for (var i = linkShape.length - 1; i >= 0; i--) {
                    newLink.set(linkShape[i], true);
                };
            }
            this.linksById[type] = newLink;

            // Ñapping edges
            if (type == "brk_fix") {
                newLink.set("vertices", [{ x: 255, y: 155 }]);
            } else if (type == "fix_brk") {
                newLink.set("vertices", [{ x: 280, y: 200 }]);
            } else if (type == "fai_fai") {
                newLink.set("vertices", [{ x: 502, y: 317 }]);
            }

            return newLink;
        }.bind(this);

        graph.addCell([ passedBall, brokenBall, fixedBall, failedBall]);

        graph.addCell([
            link(passedBall, passedBall, "pas_pas", ["manhattan", "smooth"], 'green'),
            link(passedBall, brokenBall, "pas_brk", null, 'red'),
            link(brokenBall, fixedBall, "brk_fix", null, 'green'),
            link(brokenBall, failedBall, "brk_fai", null, 'red'),
            link(fixedBall, passedBall, "fix_pas", null, 'green'),
            link(fixedBall, brokenBall, "fix_brk", ["smooth"], 'red', 0),
            link(failedBall, failedBall, "fai_fai", ["manhattan", "smooth"], 'red'),
            link(failedBall, fixedBall, "fai_fix", null, 'green', -0.1)
        ]);

        // TODO add custom events and remov cell:mouseover joinjs event

        var allBalls = this.element.find('.Place');
        allBalls.qtip(
            {
                id: 'sampletooltip',
                content: {
                    text: 'Hi. I am a sample tooltip!',
                    title: 'Sample tooltip'
                },
                show: {
                    event: 'mouseenter'
                },
                hide: {
                    event: 'mouseleave unfocus'
                },
                position: {
                    my: 'top center',
                    at: 'bottom center'
                },
                style: {
                    classes: 'qtip-bootstrap',
                    tip: {
                        width: 16,
                        height: 6
                    }
                }
            }
        );

        function fireTransition(t, sec) {

            var allbound = graph.getConnectedLinks(t);
            var elemcolors = [];
            var color;
            for (var i = 0; i < allbound.length; i++) {
                var source = graph.getCell(allbound[i].get('source').id);
                var target = graph.getCell(allbound[i].get('target').id);
                var sourceid = source.attributes.tokens;
                var targetid = target.attributes.tokens;
                if (targetid == "Failed" || targetid == "Broken") {
                    color = "red";
                } else {
                    color = "green";
                }
                elemcolors.push({target : target, source : source, color: color});
            }

            var isFirable = true;

            if (isFirable) {
                _.each(elemcolors,
                    function(p) {
                        var link = _.find(allbound, function(l) {
                        return (l.get('target').id === p.target.id) && (l.get('source').id === p.source.id)
                    });
                    paper.findViewByModel(link).sendToken(joint.Vectorizer('circle', { r: 7, fill: p.color }).node, sec * 1300);
                });
            }
        }

        function simulate() {
            var transitions = [passedBall, failedBall, brokenBall, fixedBall];
            _.each(transitions, function(t) { fireTransition(t, 1); });
            return setInterval(function() {
                _.each(transitions, function(t) { fireTransition(t, 1); });
            }, 2000);
        }

        function stopSimulation(simulationId) {
            clearInterval(simulationId);
        }

        var simulationId = simulate();

        updateValues.call(this, normalizedData);
        //}).bind(this);
    };

    window.framework.widgets.BuildTransitions = BuildTransitions;

    // AMD compliant
    if ( typeof define === "function" && define.amd) {
        define( ['joint'], function (_joint) {
            joint = _joint;
            return BuildTransitions;
        });
    }

})();<|endoftext|>


<commit_before>import { getState } from './store';

function connect(mapStateToParams, mergeParams) {
    let mapStateToParamsFunc = mapStateToParams;
    let mergeParamsFunc = mergeParams;
    if (typeof mapStateToParamsFunc !== 'function') {
        mapStateToParamsFunc = () => ({});
    }
    if (typeof mergeParamsFunc !== 'function') {
        mergeParamsFunc = (stateParams, ownParams) => Object.assign({}, ownParams, stateParams);
    }
    return function(ViewModel) {
        if (typeof ViewModel !== 'function') {
            throw new TypeError('ViewModel must be function.');
        }
        return function (ownParams) {
            const state = getState();
            const stateParams = mapStateToParamsFunc(state(), ownParams);
            const mergedParams = mergeParamsFunc(stateParams, ownParams);
            return new ViewModel(mergedParams);
        };
    };
}

export default connect;
<commit_msg>Dont create a new default function every time connect is called without parameters.<commit_after>import { getState } from './store';

const defaultMapStateToParams = () => ({});
const defaultMergeParams = (stateParams, ownParams) =>
  Object.assign({}, ownParams, stateParams);

const connect = (mapStateToParams, mergeParams) => {
  let mapStateToParamsFunc = mapStateToParams;
  let mergeParamsFunc = mergeParams;
  if (typeof mapStateToParamsFunc !== 'function') {
    mapStateToParamsFunc = defaultMapStateToParams;
  }
  if (typeof mergeParamsFunc !== 'function') {
    mergeParamsFunc = defaultMergeParams;
  }
  return ViewModel => {
    if (typeof ViewModel !== 'function') {
      throw new TypeError('ViewModel must be function.');
    }
    return ownParams => {
      const state = getState();
      const stateParams = mapStateToParamsFunc(state(), ownParams);
      const mergedParams = mergeParamsFunc(stateParams, ownParams);
      return new ViewModel(mergedParams);
    };
  };
};

export default connect;
<|endoftext|>


<commit_before>import {Component, createElement} from 'react';
import {oneflow} from './oneflow';

const connectAvance = (flowInstance) => (wrapped, stateInjector = true, actionInjector = {}, watch) => {
    let stateflow
    if (stateInjector) {
        stateflow = flowInstance.state$;
        if (watch instanceof Array && watch.length) {
            stateflow = stateflow.filter(({update}) => watch.findIndex(el => update.hasOwnProperty(el)) !== -1)
        }
        if (stateInjector === true) {
            stateflow = stateflow.map(({state}) => state)
        }
        else if (stateInjector instanceof Function) {
            stateflow = stateflow.map(({state}) => stateInjector(state))
        }
    }

    let actionHandlers = {};
    for ( const key in actionInjector ) {
        actionHandlers[key] = flowInstance.action(actionInjector[key])
    }

    class Connect extends Component {
        componentWillMount() {
            if (stateflow) {
                this.subscription = stateflow.subscribe(state => this.setState(state));
            }
        }

        componentWillUnmount() {
            if (this.subscription) {
                this.subscription.unsubscribe();
            }
        }

        render() {
            return createElement(wrapped, Object.assign({}, this.state, actionHandlers, this.props));
        }

        static getWrappedComponent() {
            return wrapped;
        }
    }
    return Connect;
}

const connect = connectAvance(oneflow);

export {connectAvance, connect};
<commit_msg>Enrich meta info in connected actionInjector<commit_after>import {Component, createElement} from 'react';
import {oneflow} from './oneflow';

const connectAvance = (flowInstance) => (wrapped, stateInjector = true, actionInjector = {}, watch) => {
    let stateflow
    if (stateInjector) {
        stateflow = flowInstance.state$;
        if (watch instanceof Array && watch.length) {
            stateflow = stateflow.filter(({update}) => watch.findIndex(el => update.hasOwnProperty(el)) !== -1)
        }
        if (stateInjector === true) {
            stateflow = stateflow.map(({state}) => state)
        }
        else if (stateInjector instanceof Function) {
            stateflow = stateflow.map(({state}) => stateInjector(state))
        }
    }

    let actionHandlers = {};
    for ( const key in actionInjector ) {
        actionHandlers[key] = flowInstance.action(actionInjector[key], `${actionInjector[key].name}[${flowInstance.name}:${key}]`)
    }

    class Connect extends Component {
        componentWillMount() {
            if (stateflow) {
                this.subscription = stateflow.subscribe(state => this.setState(state));
            }
        }

        componentWillUnmount() {
            if (this.subscription) {
                this.subscription.unsubscribe();
            }
        }

        render() {
            return createElement(wrapped, Object.assign({}, this.state, actionHandlers, this.props));
        }

        static getWrappedComponent() {
            return wrapped;
        }
    }
    return Connect;
}

const connect = connectAvance(oneflow);

export {connectAvance, connect};
<|endoftext|>


<commit_before>(function($) {
  'use strict';

  $(function() {
    ///////////////////////////////////
    // Initialize singletap on buttons:
    ///////////////////////////////////
    $('body').on('singletap', '.button', function() {
      var $this = $(this);
      if ($this.parent('.segmented')[0]) return;
      $this.addClass('selected');
      setTimeout(function() {
        $this.removeClass('selected');
      }, 500);
    });
  });
})(window.jQuery);<commit_msg>Fixed bug where tabbar buttons were not staying selected.<commit_after>(function($) {
  'use strict';

  $(function() {
    ///////////////////////////////////
    // Initialize singletap on buttons:
    ///////////////////////////////////
    $('body').on('singletap', '.button', function() {
      var $this = $(this);
      if ($this.parent('.segmented')[0]) return;
      if ($this.parent('.tabbar')[0]) return;
      $this.addClass('selected');
      setTimeout(function() {
        $this.removeClass('selected');
      }, 500);
    });
  });
})(window.jQuery);<|endoftext|>


<commit_before>var PlanningPoker = angular.module('PlanningPoker', ['chart.js']);

PlanningPoker.config(['$httpProvider', function ($httpProvider) {
    if (!$httpProvider.defaults.headers.get) {
        $httpProvider.defaults.headers.get = {}
    }
    //prevent IE caching AJAX requests
    $httpProvider.defaults.headers.get['If-Modified-Since'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
    $httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
    $httpProvider.defaults.headers.get['Pragma'] = 'no-cache';
}]);

PlanningPoker.directive('showFocus', function ($timeout) {
    return function (scope, element, attrs) {
        scope.$watch(attrs.showFocus,
            function (newValue) {
                $timeout(function () {
                    newValue && element[0].focus();
                });
            }, true);
    };
});

PlanningPoker.filter('userNameCaseFilter', function () {
    return function (input) {
        if (!input) {
            return ''
        }
        else if (input.length > 2) {
            return input.replace(/\w\S*/g, function (txt) {
                return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
            });
        } else {
            return input.toUpperCase();
        }
    }
});

PlanningPoker.controller('PokerCtrl', ['$scope', '$http', function ($scope, $http) {
    $scope.userName = '';
    $scope.inSession = false;
    $scope.voted = false;
    $scope.sessionUsers = [];
    $scope.notVoted = [];
    $scope.votedUsers = [];
    $scope.waitingFor = [];
    $scope.votingResults = [];
    $scope.resultsdata = [];
    $scope.isAdmin = false;
    $scope.inSession = false;
    $scope.loading = false;
    $scope.currentItem = 'the current item';
    $scope.itemInput = undefined;

    $scope.legalEstimates = [0.5, 1, 2, 3, 5, 8, 13, 20, 100];
    $scope.labels = $scope.legalEstimates.map(String);

    $scope.createSession = function () {
        $scope.loading = true;
        $http({
            method: 'POST',
            url: '/createSession',
            params: {
                userName: $scope.userName
            }
        }).success(function (response) {
            $scope.loading = false;
            $scope.inSession = true;
            $scope.sessionId = response;
            $scope.isAdmin = true;
            var socket = new SockJS('/stomp');
            var stompClient = Stomp.over(socket);
            stompClient.debug = null;
            stompClient.connect({}, function (frame) {
                stompClient.subscribe("/topic/results/" + response, function (data) {
                    $scope.$apply(function () {
                        var message = JSON.parse(data.body);
                        $scope.resultsdata = $scope.aggregateResults(message);
                        $scope.votedUsers = message.map(function (estimate) {
                            return estimate.userName.toUpperCase();
                        });
                        $scope.waitingFor = $scope.notVoted.filter(function (user) {
                            return $scope.votedUsers.indexOf(user.toUpperCase()) < 0;
                        });
                    });
                });
                stompClient.subscribe("/topic/users/" + response, function (data) {
                    $scope.$apply(function () {
                        var users = JSON.parse(data.body);
                        $scope.sessionUsers = users;
                        $scope.notVoted = users;
                        $scope.waitingFor = $scope.notVoted.filter(function (user) {
                            return $scope.votedUsers.indexOf(user.toUpperCase()) < 0;
                        });
                    });
                });
                stompClient.subscribe("/topic/item/" + $scope.sessionId, function (data) {
                    $scope.$apply(function () {
                        $scope.currentItem = data.body
                    });
                });
            });
        });
    };

    $scope.joinSession = function () {
        if (!$scope.sessionId) {
            alert("Please enter a valid session ID.")
        } else {
            $scope.loading = true;
            $scope.inSession = true;
            var socket = new SockJS('/stomp');
            var stompClient = Stomp.over(socket);
            stompClient.debug = null;
            stompClient.connect({}, function (frame) {
                stompClient.subscribe("/topic/users/" + $scope.sessionId, function (data) {
                    $scope.$apply(function () {
                        var users = JSON.parse(data.body);
                        $scope.sessionUsers = users;
                        $scope.notVoted = users;
                        $scope.waitingFor = $scope.notVoted.filter(function (user) {
                            return $scope.votedUsers.indexOf(user.toUpperCase()) < 0;
                        });
                    });
                });
                stompClient.subscribe("/topic/results/" + $scope.sessionId, function (data) {
                    $scope.$apply(function () {
                        var message = JSON.parse(data.body);
                        if (message.length == 0) {
                            $scope.voted = false;
                            $scope.currentItem = 'the current item'
                        } else {
                            $scope.resultsdata = $scope.aggregateResults(message);
                            $scope.votedUsers = message.map(function (estimate) {
                                return estimate.userName.toUpperCase();
                            });
                            $scope.waitingFor = $scope.notVoted.filter(function (user) {
                                return $scope.votedUsers.indexOf(user.toUpperCase()) < 0;
                            });
                        }
                    });
                });
                stompClient.subscribe("/topic/item/" + $scope.sessionId, function (data) {
                    $scope.$apply(function () {
                        $scope.currentItem = data.body
                    });
                });
                $http({
                    method: 'POST',
                    url: '/joinSession',
                    params: {
                        sessionId: $scope.sessionId,
                        userName: $scope.userName
                    }
                }).then(function successCallback(response) {
                    $scope.loading = false;
                }, function errorCallback(response) {
                    $scope.loading = false;
                    if (response.data.message == 'user exists') {
                        alert("A user with that name has already registered for this session.");
                        $scope.userName = ''
                    } else {
                        alert("Session " + $scope.sessionId + " has not yet been started. " +
                            "Please try again in a few seconds, or start a new session as moderator.")
                    }
                    $scope.voted = false;
                    $scope.inSession = false;
                    $scope.loading = false;
                    $scope.isAdmin = false;
                });
            });
        }
    };

    $scope.vote = function (estimateValue) {
        $scope.voted = true;
        $scope.loading = true;
        $http({
            method: 'POST',
            url: '/vote',
            params: {
                sessionId: $scope.sessionId,
                userName: $scope.userName,
                estimateValue: estimateValue
            }
        }).then(
            function successCallback(response) {
                $scope.loading = false;
            },
            function errorCallback(response) {
                alert("Session " + $scope.sessionId + " is not currently active.");
                $scope.voted = false;
                $scope.inSession = false;
                $scope.loading = false;
                $scope.isAdmin = false;
                $scope.sessionId = undefined;
            }
        );
    };

    $scope.setCurrentItem = function () {
        $http({
            method: 'POST',
            url: '/setCurrentItem',
            params: {
                sessionId: $scope.sessionId,
                item: $scope.itemInput
            }
        })
    };

    $scope.refresh = function () {
        $http({
            method: 'GET',
            url: '/refresh',
            params: {
                sessionId: $scope.sessionId
            }
        })
    };

    $scope.reset = function () {
        $scope.loading = true;
        $http({
            method: 'DELETE',
            url: '/reset',
            params: {
                sessionId: $scope.sessionId,
                userName: $scope.userName
            }
        }).success(function (response) {
            $scope.loading = false;
            $scope.voted = false;
            $scope.itemInput = undefined;
            $scope.currentItem = 'the current item';
        })
    };

    $scope.aggregateResults = function (result) {
        $scope.votingResults = result;
        var estimates = $scope.votingResults.map(function (val) {
            return val.estimateValue
        });
        return $scope.legalEstimates.map(function (x) {
            return estimates.filter(function (y) {
                return x == y
            }).length
        })
    };

}]);

<commit_msg>formatting on results page when no current item is set<commit_after>var PlanningPoker = angular.module('PlanningPoker', ['chart.js']);

PlanningPoker.config(['$httpProvider', function ($httpProvider) {
    if (!$httpProvider.defaults.headers.get) {
        $httpProvider.defaults.headers.get = {}
    }
    //prevent IE caching AJAX requests
    $httpProvider.defaults.headers.get['If-Modified-Since'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
    $httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
    $httpProvider.defaults.headers.get['Pragma'] = 'no-cache';
}]);

PlanningPoker.directive('showFocus', function ($timeout) {
    return function (scope, element, attrs) {
        scope.$watch(attrs.showFocus,
            function (newValue) {
                $timeout(function () {
                    newValue && element[0].focus();
                });
            }, true);
    };
});

PlanningPoker.filter('userNameCaseFilter', function () {
    return function (input) {
        if (!input) {
            return ''
        }
        else if (input.length > 2) {
            return input.replace(/\w\S*/g, function (txt) {
                return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
            });
        } else {
            return input.toUpperCase();
        }
    }
});

PlanningPoker.controller('PokerCtrl', ['$scope', '$http', function ($scope, $http) {
    $scope.userName = '';
    $scope.inSession = false;
    $scope.voted = false;
    $scope.sessionUsers = [];
    $scope.notVoted = [];
    $scope.votedUsers = [];
    $scope.waitingFor = [];
    $scope.votingResults = [];
    $scope.resultsdata = [];
    $scope.isAdmin = false;
    $scope.inSession = false;
    $scope.loading = false;
    $scope.currentItem = 'the current item';
    $scope.itemInput = undefined;

    $scope.legalEstimates = [0.5, 1, 2, 3, 5, 8, 13, 20, 100];
    $scope.labels = $scope.legalEstimates.map(String);

    $scope.createSession = function () {
        $scope.loading = true;
        $http({
            method: 'POST',
            url: '/createSession',
            params: {
                userName: $scope.userName
            }
        }).success(function (response) {
            $scope.loading = false;
            $scope.inSession = true;
            $scope.sessionId = response;
            $scope.isAdmin = true;
            var socket = new SockJS('/stomp');
            var stompClient = Stomp.over(socket);
            stompClient.debug = null;
            stompClient.connect({}, function (frame) {
                stompClient.subscribe("/topic/results/" + response, function (data) {
                    $scope.$apply(function () {
                        var message = JSON.parse(data.body);
                        $scope.resultsdata = $scope.aggregateResults(message);
                        $scope.votedUsers = message.map(function (estimate) {
                            return estimate.userName.toUpperCase();
                        });
                        $scope.waitingFor = $scope.notVoted.filter(function (user) {
                            return $scope.votedUsers.indexOf(user.toUpperCase()) < 0;
                        });
                    });
                });
                stompClient.subscribe("/topic/users/" + response, function (data) {
                    $scope.$apply(function () {
                        var users = JSON.parse(data.body);
                        $scope.sessionUsers = users;
                        $scope.notVoted = users;
                        $scope.waitingFor = $scope.notVoted.filter(function (user) {
                            return $scope.votedUsers.indexOf(user.toUpperCase()) < 0;
                        });
                    });
                });
                stompClient.subscribe("/topic/item/" + $scope.sessionId, function (data) {
                    $scope.$apply(function () {
                        $scope.currentItem = data.body
                    });
                });
            });
        });
    };

    $scope.joinSession = function () {
        if (!$scope.sessionId) {
            alert("Please enter a valid session ID.")
        } else {
            $scope.loading = true;
            $scope.inSession = true;
            var socket = new SockJS('/stomp');
            var stompClient = Stomp.over(socket);
            stompClient.debug = null;
            stompClient.connect({}, function (frame) {
                stompClient.subscribe("/topic/users/" + $scope.sessionId, function (data) {
                    $scope.$apply(function () {
                        var users = JSON.parse(data.body);
                        $scope.sessionUsers = users;
                        $scope.notVoted = users;
                        $scope.waitingFor = $scope.notVoted.filter(function (user) {
                            return $scope.votedUsers.indexOf(user.toUpperCase()) < 0;
                        });
                    });
                });
                stompClient.subscribe("/topic/results/" + $scope.sessionId, function (data) {
                    $scope.$apply(function () {
                        var message = JSON.parse(data.body);
                        if (message.length == 0) {
                            $scope.voted = false;
                            $scope.currentItem = 'the current item'
                        } else {
                            $scope.resultsdata = $scope.aggregateResults(message);
                            $scope.votedUsers = message.map(function (estimate) {
                                return estimate.userName.toUpperCase();
                            });
                            $scope.waitingFor = $scope.notVoted.filter(function (user) {
                                return $scope.votedUsers.indexOf(user.toUpperCase()) < 0;
                            });
                        }
                    });
                });
                stompClient.subscribe("/topic/item/" + $scope.sessionId, function (data) {
                    $scope.$apply(function () {
                        $scope.currentItem = data.body
                    });
                });
                $http({
                    method: 'POST',
                    url: '/joinSession',
                    params: {
                        sessionId: $scope.sessionId,
                        userName: $scope.userName
                    }
                }).then(function successCallback(response) {
                    $scope.loading = false;
                }, function errorCallback(response) {
                    $scope.loading = false;
                    if (response.data.message == 'user exists') {
                        alert("A user with that name has already registered for this session.");
                        $scope.userName = ''
                    } else {
                        alert("Session " + $scope.sessionId + " has not yet been started. " +
                            "Please try again in a few seconds, or start a new session as moderator.")
                    }
                    $scope.voted = false;
                    $scope.inSession = false;
                    $scope.loading = false;
                    $scope.isAdmin = false;
                });
            });
        }
    };

    $scope.vote = function (estimateValue) {
        $scope.voted = true;
        $scope.loading = true;
        if (!$scope.itemInput) {
            $scope.itemInput = 'Results';
            $scope.setCurrentItem();
        }
        $http({
            method: 'POST',
            url: '/vote',
            params: {
                sessionId: $scope.sessionId,
                userName: $scope.userName,
                estimateValue: estimateValue
            }
        }).then(
            function successCallback(response) {
                $scope.loading = false;
            },
            function errorCallback(response) {
                alert("Session " + $scope.sessionId + " is not currently active.");
                $scope.voted = false;
                $scope.inSession = false;
                $scope.loading = false;
                $scope.isAdmin = false;
                $scope.sessionId = undefined;
            }
        );
    };

    $scope.setCurrentItem = function () {
        $http({
            method: 'POST',
            url: '/setCurrentItem',
            params: {
                sessionId: $scope.sessionId,
                item: $scope.itemInput
            }
        })
    };

    $scope.refresh = function () {
        $http({
            method: 'GET',
            url: '/refresh',
            params: {
                sessionId: $scope.sessionId
            }
        })
    };

    $scope.reset = function () {
        $scope.loading = true;
        $http({
            method: 'DELETE',
            url: '/reset',
            params: {
                sessionId: $scope.sessionId,
                userName: $scope.userName
            }
        }).success(function (response) {
            $scope.loading = false;
            $scope.voted = false;
            $scope.itemInput = undefined;
            $scope.currentItem = 'the current item';
        })
    };

    $scope.aggregateResults = function (result) {
        $scope.votingResults = result;
        var estimates = $scope.votingResults.map(function (val) {
            return val.estimateValue
        });
        return $scope.legalEstimates.map(function (x) {
            return estimates.filter(function (y) {
                return x == y
            }).length
        })
    };

}]);

<|endoftext|>


<commit_before>import { test } from 'substance-test'
import {
  openManuscriptEditor, getDocument, getSelectionState, setSelection, fixture,
  openContextMenuAndFindTool, openMenuAndFindTool, getSelection, selectNode
} from './shared/integrationTestHelpers'
import setupTestApp from './shared/setupTestApp'
import { getLabel } from '../index'

const emptyLabel = '???'
const manuscriptFootnoteSelector = '.sc-manuscript > .sc-manuscript-section.sm-footnotes .sc-footnote'
const tableFootnoteSelector = '.sc-table-figure > .se-footnotes > .sc-footnote'
const tableFootnoteContentXpath = ['article', 'body', 'table-figure', 'footnote', 'paragraph']

test('Footnotes: add a footnote while selection is in the manuscript body', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  setSelection(editor, 'p-0.content', 1)
  _insertFootnote(editor)
  t.equal(_getManuscriptFootnotes(editor).length, 2, 'there should be two manuscript footnotes')
  let xpath = _getCurrentXpath(editor)
  let footnoteEntry = xpath.find(e => e.type === 'footnote')
  let sel = getSelection(editor)
  t.notNil(footnoteEntry, 'selection should be inside manuscript footnote')
  t.deepEqual({
    surfaceId: sel.surfaceId,
    containerPath: sel.containerPath
  }, {
    surfaceId: `${footnoteEntry.id}.content`,
    containerPath: [footnoteEntry.id, 'content']
  }, 'selection should have correct surfaceId and containerPath')
  t.end()
})

test('Footnotes: add a footnote while selection is on a table figure', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  selectNode(editor, 'table-1')
  _insertFootnote(editor)
  t.equal(_getTableFootnotes(editor).length, 3, 'there should be three table footnotes')
  t.deepEqual(_getCurrentXpathTypes(editor), tableFootnoteContentXpath, 'selection should be inside table footnote')
  t.end()
})

test('Footnotes: add a footnote while selection is inside a table cell', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  // set selection inside table cell
  setSelection(editor, 't-1_2_1.content', 1)
  _insertFootnote(editor)
  t.equal(_getTableFootnotes(editor).length, 3, 'there should be three table footnotes')
  t.deepEqual(_getCurrentXpathTypes(editor), tableFootnoteContentXpath, 'selection should be inside table footnote')
  t.end()
})

test('Footnotes: add a footnote while selection is on a table cell', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  const editorSession = editor.editorSession
  // set selection on table cell
  editorSession.setSelection({
    type: 'custom',
    customType: 'table',
    nodeId: 't-1',
    surfaceId: 't-1',
    data: {
      anchorCellId: 't-1_2_2',
      focusCellId: 't-1_2_2'
    }
  })
  _insertFootnote(editor)
  t.equal(_getTableFootnotes(editor).length, 3, 'there should be three table footnotes')
  t.deepEqual(_getCurrentXpathTypes(editor), tableFootnoteContentXpath, 'selection should be inside table footnote')
  t.end()
})

test('Footnotes: add a footnote in the manuscript while selection is inside a table caption', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  // set selection inside table figure caption
  setSelection(editor, 'table-1-caption-p-1.content', 1)
  _insertFootnote(editor)
  t.equal(_getTableFootnotes(editor).length, 3, 'there should be three table footnotes')
  t.deepEqual(_getCurrentXpathTypes(editor), tableFootnoteContentXpath, 'selection should be inside table footnote')
  t.end()
})

test('Footnotes: remove a manuscript footnote', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  let doc = getDocument(editor)
  const getFirstFootnote = () => editor.find('[data-id=fn-1]')
  const getFirstFootnoteXref = () => editor.find('[data-id=p1-xref-1]')
  t.notNil(getFirstFootnote(), 'the footnote should be visible')
  setSelection(editor, 'fn-1-p-1.content', 1)
  const removeTool = openContextMenuAndFindTool(editor, '.sm-remove-footnote')
  t.ok(removeTool, 'there should be a remove button')
  removeTool.click()
  t.isNil(doc.get('fn-1'), 'the footnote should have been removed from the model')
  t.isNil(editor.find('[data-id=fn-1]'), '.. and should not be visible anymore')
  t.equal(getFirstFootnoteXref().text(), emptyLabel, 'xref label should not contain reference')
  t.end()
})

test('Footnotes: remove a table footnote', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  let doc = getDocument(editor)
  t.notNil(editor.find('[data-id=table-1-fn-1]'), 'the table footnote should be visible')
  setSelection(editor, 'table-1-fn-1-p-1.content', 1)
  const removeTool = openContextMenuAndFindTool(editor, '.sm-remove-footnote')
  t.ok(removeTool, 'there should be a remove button')
  removeTool.click()
  t.isNil(doc.get('table-1-fn-1'), 'the table footnote should have been removed from the model')
  t.isNil(editor.find('[data-id=table-1-fn-1]'), '.. and should not be visible anymore')
  t.end()
})

test('Footnotes: reference a footnote from a paragraph in the manuscript body', t => {
  _testInsertXref(t, ['p-0', 'content'], 'footnote', 'fn', 'fn-1', '1')
})

test('Footnotes: reference a footnote from a table-cell', t => {
  _testInsertXref(t, ['t-1_2_1', 'content'], 'footnote', 'table-fn', 'table-1-fn-1', '*')
})

test('Footnotes: reference a footnote from a table caption', t => {
  _testInsertXref(t, ['table-1-caption-p-1', 'content'], 'footnote', 'table-fn', 'table-1-fn-1', '*')
})

test('Footnotes: add a reference citation to a paragraph in the body', t => {
  _testInsertXref(t, ['p-0', 'content'], 'bibr', 'bibr', 'r-1', '[1]')
})

test('Footnotes: add a reference citation into a table title', t => {
  _testInsertXref(t, ['table-1', 'title'], 'bibr', 'bibr', 'r-1', '[1]')
})

test('Footnotes: add a reference citation into a table caption', t => {
  _testInsertXref(t, ['table-1-caption-p-1', 'content'], 'bibr', 'bibr', 'r-1', '[1]')
})

test('Footnotes: add a reference citation into a table cell', t => {
  _testInsertXref(t, ['t-1_2_1', 'content'], 'bibr', 'bibr', 'r-1', '[1]')
})

function _testInsertXref (t, path, refTool, refType, rid, label) {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  let doc = getDocument(editor)
  setSelection(editor, path, 1)
  _insertCrossRef(editor, refTool, rid)
  let annos = doc.getAnnotations(path)
  let xref = annos[0]
  if (xref) {
    let actual = {
      type: xref.type,
      refType: xref.refType,
      refTargets: xref.refTargets
    }
    let expected = {
      type: 'xref',
      refType,
      refTargets: rid.split(' ')
    }
    t.deepEqual(actual, expected, 'a xref should have been created')
    t.equal(getLabel(xref), label, 'label should be correct')
  } else {
    t.fail('xref has not been created')
  }
  t.end()
}

function _insertCrossRef (editor, refType, rid) {
  let tool = openMenuAndFindTool(editor, 'insert', `.sm-insert-xref-${refType}`)
  tool.click()
  // an empty xref should have been inserted
  // click on the target option with the given node id
  editor.find(`.sc-edit-xref-tool > .se-option > *[data-id=${rid}]`).click()
}

function _insertFootnote (el) {
  const insertDropdown = el.find('.sc-tool-dropdown.sm-insert .sc-button')
  // Check if dropdown is already active
  const isDropDownOpened = insertDropdown.hasClass('sm-active')
  if (!isDropDownOpened) {
    insertDropdown.click()
  }
  let insertFootnoteBtn = el.find('.sc-tool.sm-insert-footnote')
  insertFootnoteBtn.click()
}

function _getCurrentXpath (editor) {
  const selectionState = getSelectionState(editor)
  return selectionState.xpath
}

function _getCurrentXpathTypes (editor) {
  const xpath = _getCurrentXpath(editor)
  return xpath.map(p => p.type)
}

function _getManuscriptFootnotes (editor) {
  return editor.findAll(manuscriptFootnoteSelector)
}

function _getTableFootnotes (editor, tableId) {
  return editor.findAll(tableFootnoteSelector)
}
<commit_msg>Reveal a problem with custom footnote labels.<commit_after>import { test } from 'substance-test'
import { DefaultDOMElement } from 'substance'
import {
  openManuscriptEditor, getDocument, getSelectionState, setSelection, fixture,
  openContextMenuAndFindTool, openMenuAndFindTool, getSelection, selectNode
} from './shared/integrationTestHelpers'
import { importElement } from './shared/testHelpers'
import setupTestApp from './shared/setupTestApp'
import { getLabel } from '../index'

const emptyLabel = '???'
const manuscriptFootnoteSelector = '.sc-manuscript > .sc-manuscript-section.sm-footnotes .sc-footnote'
const tableFootnoteSelector = '.sc-table-figure > .se-footnotes > .sc-footnote'
const tableFootnoteContentXpath = ['article', 'body', 'table-figure', 'footnote', 'paragraph']

const FOOTNOTE_WITH_LABEL = `
  <fn>
    Label

Footnote content

  </fn>
`

test('Footnotes: import footnote', t => {
  let el = DefaultDOMElement.parseSnippet(FOOTNOTE_WITH_LABEL.trim(), 'xml')
  let footnoteNode = importElement(el)
  t.equal(footnoteNode.label, '', 'custom label should be ignored during import')
  t.end()
})

test('Footnotes: add a footnote while selection is in the manuscript body', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  setSelection(editor, 'p-0.content', 1)
  _insertFootnote(editor)
  t.equal(_getManuscriptFootnotes(editor).length, 2, 'there should be two manuscript footnotes')
  let xpath = _getCurrentXpath(editor)
  let footnoteEntry = xpath.find(e => e.type === 'footnote')
  let sel = getSelection(editor)
  t.notNil(footnoteEntry, 'selection should be inside manuscript footnote')
  t.deepEqual({
    surfaceId: sel.surfaceId,
    containerPath: sel.containerPath
  }, {
    surfaceId: `${footnoteEntry.id}.content`,
    containerPath: [footnoteEntry.id, 'content']
  }, 'selection should have correct surfaceId and containerPath')
  t.end()
})

test('Footnotes: add a footnote while selection is on a table figure', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  selectNode(editor, 'table-1')
  _insertFootnote(editor)
  t.equal(_getTableFootnotes(editor).length, 3, 'there should be three table footnotes')
  t.deepEqual(_getCurrentXpathTypes(editor), tableFootnoteContentXpath, 'selection should be inside table footnote')
  t.end()
})

test('Footnotes: add a footnote while selection is inside a table cell', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  // set selection inside table cell
  setSelection(editor, 't-1_2_1.content', 1)
  _insertFootnote(editor)
  t.equal(_getTableFootnotes(editor).length, 3, 'there should be three table footnotes')
  t.deepEqual(_getCurrentXpathTypes(editor), tableFootnoteContentXpath, 'selection should be inside table footnote')
  t.end()
})

test('Footnotes: add a footnote while selection is on a table cell', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  const editorSession = editor.editorSession
  // set selection on table cell
  editorSession.setSelection({
    type: 'custom',
    customType: 'table',
    nodeId: 't-1',
    surfaceId: 't-1',
    data: {
      anchorCellId: 't-1_2_2',
      focusCellId: 't-1_2_2'
    }
  })
  _insertFootnote(editor)
  t.equal(_getTableFootnotes(editor).length, 3, 'there should be three table footnotes')
  t.deepEqual(_getCurrentXpathTypes(editor), tableFootnoteContentXpath, 'selection should be inside table footnote')
  t.end()
})

test('Footnotes: add a footnote in the manuscript while selection is inside a table caption', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  // set selection inside table figure caption
  setSelection(editor, 'table-1-caption-p-1.content', 1)
  _insertFootnote(editor)
  t.equal(_getTableFootnotes(editor).length, 3, 'there should be three table footnotes')
  t.deepEqual(_getCurrentXpathTypes(editor), tableFootnoteContentXpath, 'selection should be inside table footnote')
  t.end()
})

test('Footnotes: remove a manuscript footnote', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  let doc = getDocument(editor)
  const getFirstFootnote = () => editor.find('[data-id=fn-1]')
  const getFirstFootnoteXref = () => editor.find('[data-id=p1-xref-1]')
  t.notNil(getFirstFootnote(), 'the footnote should be visible')
  setSelection(editor, 'fn-1-p-1.content', 1)
  const removeTool = openContextMenuAndFindTool(editor, '.sm-remove-footnote')
  t.ok(removeTool, 'there should be a remove button')
  removeTool.click()
  t.isNil(doc.get('fn-1'), 'the footnote should have been removed from the model')
  t.isNil(editor.find('[data-id=fn-1]'), '.. and should not be visible anymore')
  t.equal(getFirstFootnoteXref().text(), emptyLabel, 'xref label should not contain reference')
  t.end()
})

test('Footnotes: remove a table footnote', t => {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  let doc = getDocument(editor)
  t.notNil(editor.find('[data-id=table-1-fn-1]'), 'the table footnote should be visible')
  setSelection(editor, 'table-1-fn-1-p-1.content', 1)
  const removeTool = openContextMenuAndFindTool(editor, '.sm-remove-footnote')
  t.ok(removeTool, 'there should be a remove button')
  removeTool.click()
  t.isNil(doc.get('table-1-fn-1'), 'the table footnote should have been removed from the model')
  t.isNil(editor.find('[data-id=table-1-fn-1]'), '.. and should not be visible anymore')
  t.end()
})

test('Footnotes: reference a footnote from a paragraph in the manuscript body', t => {
  _testInsertXref(t, ['p-0', 'content'], 'footnote', 'fn', 'fn-1', '1')
})

test('Footnotes: reference a footnote from a table-cell', t => {
  _testInsertXref(t, ['t-1_2_1', 'content'], 'footnote', 'table-fn', 'table-1-fn-1', '*')
})

test('Footnotes: reference a footnote from a table caption', t => {
  _testInsertXref(t, ['table-1-caption-p-1', 'content'], 'footnote', 'table-fn', 'table-1-fn-1', '*')
})

test('Footnotes: add a reference citation to a paragraph in the body', t => {
  _testInsertXref(t, ['p-0', 'content'], 'bibr', 'bibr', 'r-1', '[1]')
})

test('Footnotes: add a reference citation into a table title', t => {
  _testInsertXref(t, ['table-1', 'title'], 'bibr', 'bibr', 'r-1', '[1]')
})

test('Footnotes: add a reference citation into a table caption', t => {
  _testInsertXref(t, ['table-1-caption-p-1', 'content'], 'bibr', 'bibr', 'r-1', '[1]')
})

test('Footnotes: add a reference citation into a table cell', t => {
  _testInsertXref(t, ['t-1_2_1', 'content'], 'bibr', 'bibr', 'r-1', '[1]')
})

function _testInsertXref (t, path, refTool, refType, rid, label) {
  let { app } = setupTestApp(t, fixture('cross-references'))
  let editor = openManuscriptEditor(app)
  let doc = getDocument(editor)
  setSelection(editor, path, 1)
  _insertCrossRef(editor, refTool, rid)
  let annos = doc.getAnnotations(path)
  let xref = annos[0]
  if (xref) {
    let actual = {
      type: xref.type,
      refType: xref.refType,
      refTargets: xref.refTargets
    }
    let expected = {
      type: 'xref',
      refType,
      refTargets: rid.split(' ')
    }
    t.deepEqual(actual, expected, 'a xref should have been created')
    t.equal(getLabel(xref), label, 'label should be correct')
  } else {
    t.fail('xref has not been created')
  }
  t.end()
}

function _insertCrossRef (editor, refType, rid) {
  let tool = openMenuAndFindTool(editor, 'insert', `.sm-insert-xref-${refType}`)
  tool.click()
  // an empty xref should have been inserted
  // click on the target option with the given node id
  editor.find(`.sc-edit-xref-tool > .se-option > *[data-id=${rid}]`).click()
}

function _insertFootnote (el) {
  const insertDropdown = el.find('.sc-tool-dropdown.sm-insert .sc-button')
  // Check if dropdown is already active
  const isDropDownOpened = insertDropdown.hasClass('sm-active')
  if (!isDropDownOpened) {
    insertDropdown.click()
  }
  let insertFootnoteBtn = el.find('.sc-tool.sm-insert-footnote')
  insertFootnoteBtn.click()
}

function _getCurrentXpath (editor) {
  const selectionState = getSelectionState(editor)
  return selectionState.xpath
}

function _getCurrentXpathTypes (editor) {
  const xpath = _getCurrentXpath(editor)
  return xpath.map(p => p.type)
}

function _getManuscriptFootnotes (editor) {
  return editor.findAll(manuscriptFootnoteSelector)
}

function _getTableFootnotes (editor, tableId) {
  return editor.findAll(tableFootnoteSelector)
}
<|endoftext|>