<commit_before>Midas.Config = {

  /* The stylesheet to load for the skin of the toolbar/editable regions.
   */
  stylesheet: '/stylesheets/midas.css',

  /* Toolbars
   *
   * Any object you put in here will create a new toolbar.
   *
   * button format: [label, description, [type, action], [type, action], etc]
   * type can be:
   *   'button' (default) calls handleCommand and passes the key of the object (eg. save, preview, undo etc.)
   *   'toggle' will toggle on or off when clicked (and otherwise behaves like a button)
   *   'dialog' will open a dialog window, expects the action to be:
   *     a string url
   *     a function that returns a string url
   *   'panel' will open a panel dialog, expects the action to be:
   *     a string url
   *     a function that returns a string url
   *   'palette' will open a palette window, expects the action to be:
   *     a string url
   *     a function that returns a string url
   *   'select' will open a select/pulldown style window, expects the action to be:
   *     an array
   *     a function that returns an array
   *   'context' will call a callback function, expects the action to be:
   *     a function that returns a boolean to highlight the button or not
   *     note: if a function isn't provided, the key will be passed to the
   *           contextHandler (eg. backcolor, bold, etc.), in which case a
   *           default context will be used (there are several defined in
   *           Midas.Toolbar.contexts).
   *   'mode' will toggle a given mode in the editor, expects the action to be:
   *     a string, denoting the name of the mode
   *     note: if a string isn't provided, the key will be passed to the
   *           modeHandler (eg. preview, html, etc.)
   *     note: it's assumed that when a specific "mode" is turned on, all other "modes" will be
   *           turned off (this happens automatically), thus putting the editor into a specific
   *           "state".
   *
   * If a button is an object (not an array, not a string), it's assumed that it's a button group,
   * all of it's children will be expected to be buttons or button groups.  A button group is
   * wrapped within a div for styling.  It's important to note that each of the keys, regardless of
   * if it's in a group or not needs to be unique.
   *
   * The save action is special, in that it's handled by Midas directly, all other actions are
   * handled by Midas.Region.
   *
   * Separators are any "button" that's not an array, and are expected to be a string.  You can use
   * three different separator styles: line, spacer, and flex spacer.
   * '-' = line
   * ' ' = spacer
   * '*' = flex spacer
   */
  toolbars: {
    actions: {
      save:                  ['Save', 'Save this page'],
      preview:               ['Preview', 'Preview this page', ['toggle'], ['mode']],
      sep1:                  ' ',
      undo:                  ['Undo', 'Undo your last action'],
      redo:                  ['Redo', 'Redo your last action'],
      sep2:                  ' ',
      insertlink:            ['Link', 'Insert a hyperlink', ['dialog', '/midas/link']],
      insertmedia:           ['Media', 'Insert media', ['dialog', '/midas/media']],
      inserttable:           ['Table', 'Insert a table', ['dialog', '/midas/table']],
      insertobject:          ['Object', 'Insert an object (form, widget, etc)', ['dialog', '/midas/object']],
      insertcharacter:       ['Character', 'Insert special characters', ['dialog', '/midas/character']],
      inspectorpanel:        ['Inspector', 'Open the element inspector panel', ['panel', '/midas/inspector']],
      sep3:                  '*',
      notespanel:            ['Notes', 'Open the page notes panel', ['panel', '/midas/notes']],
      historypanel:          ['History', 'Open the page history panel', ['panel', '/midas/history']]
      },
    htmleditor: {
      style:                 ['Style', '', ['select', '/midas/style.html']],
      formatblock:           ['Block Format', '', ['select', '/midas/formatblock.html']],
      sep1:                  '-',
      //backcolor:             ['Background Color', '', ['palette', '/midas/backcolor.html'], ['context']],
      forecolor:             ['Text Color', '', ['palette', '/midas/forecolor.html'], ['context']],
      sep2:                  '-',
      decoration:            {
        bold:                ['Bold', '', ['context']],
        italic:              ['Italicize', '', ['context']],
        //overline:            ['Overline', '', ['context']],
        strikethrough:       ['Strikethrough', '', ['context']],
        underline:           ['Underline', '', ['context']],
        sep:                 '-'
        },
      script:                {
        subscript:           ['Subscript', '', ['context']],
        superscript:         ['Superscript', '', ['context']],
        sep:                 '-'
        },
      justify:               {
        justifyleft:         ['Align Left', '', ['context']],
        justifycenter:       ['Center', '', ['context']],
        justifyright:        ['Align Right', '', ['context']],
        justifyfull:         ['Justify Full', '', ['context']],
        sep:                 '-'
        },
      list:                  {
        insertunorderedlist: ['Unordered List', '', ['context']],
        insertorderedlist:   ['Numbered List', '', ['context']],
        sep:                 '-'
        },
      indent:                {
        outdent:             ['Decrease Indentation', ''],
        indent:              ['Increase Indentation', ''],
        sep:                 '-'
        },
      //table:                 {
      //  insertrowbefore:     ['Insert Row', 'Insert a table row before'],
      //  insertrowafter:      ['Insert Row', 'Insert a table row after'],
      //  deleterow:           ['Delete Row', 'Delete this table row'],
      //  insertcolumnbefore:  ['Insert Column', 'Insert a table column before'],
      //  insertcolumnafter:   ['Insert Column', 'Insert a table column after'],
      //  deletecolumn:        ['Delete Column', 'Delete this table column'],
      //  sep:                 '-'
      //  },
      breaks:                {
        horizontalrule:      ['Horizontal Rule', ''],
        sep:                 '-'
        },
      removeformatting:      ['Remove Formatting', ''],
      html:                  ['Edit HTML', '', ['dialog', '/midas/html']]
      }
    },

  /* Behaviors
   *
   * Behaviors are used to change the default behaviors of the editor when a given button is
   * clicked.  For example, we prefer to add HR tags using an HR wrapped within a div with a
   * classname of hr, which allows for more flexible styling.  To add your own complex
   * behaviors just prototype them onto Midas.Region.handle.
   *
   * An example behavior would be to add a new button, called buynowbutton, and providing a
   * behavior like:
   *
   * buynowbutton: {insertElement: function() {
   *   return new Element('a', {href: '/buy-now', class: 'buy-now'}).update('Buy Now!');
   * }}
   *
   * It's important to note that the this keyword inside of the callback functions applies to an
   * instance of Midas.Region.
   *
   * Behavior Methods, and expected arguments (arguments can be provided in an array when there
   * is more than one expected):
   *   execCommand: a string of the action to take, or an array [action to take, argument]
   *   insertHTML: a callback function that returns a string
   *   ...
   */
  behaviors: {
    horizontalrule:      {insertHTML: function() {
                            return '

';
                         }}
    }
};
<commit_msg>change to how the actionsbar buttons are grouped in the config<commit_after>Midas.Config = {

  /* The stylesheet to load for the skin of the toolbar/editable regions.
   */
  stylesheet: '/stylesheets/midas.css',

  /* Toolbars
   *
   * Any object you put in here will create a new toolbar.
   *
   * button format: [label, description, [type, action], [type, action], etc]
   * type can be:
   *   'button' (default) calls handleCommand and passes the key of the object (eg. save, preview, undo etc.)
   *   'toggle' will toggle on or off when clicked (and otherwise behaves like a button)
   *   'dialog' will open a dialog window, expects the action to be:
   *     a string url
   *     a function that returns a string url
   *   'panel' will open a panel dialog, expects the action to be:
   *     a string url
   *     a function that returns a string url
   *   'palette' will open a palette window, expects the action to be:
   *     a string url
   *     a function that returns a string url
   *   'select' will open a select/pulldown style window, expects the action to be:
   *     an array
   *     a function that returns an array
   *   'context' will call a callback function, expects the action to be:
   *     a function that returns a boolean to highlight the button or not
   *     note: if a function isn't provided, the key will be passed to the
   *           contextHandler (eg. backcolor, bold, etc.), in which case a
   *           default context will be used (there are several defined in
   *           Midas.Toolbar.contexts).
   *   'mode' will toggle a given mode in the editor, expects the action to be:
   *     a string, denoting the name of the mode
   *     note: if a string isn't provided, the key will be passed to the
   *           modeHandler (eg. preview, html, etc.)
   *     note: it's assumed that when a specific "mode" is turned on, all other "modes" will be
   *           turned off (this happens automatically), thus putting the editor into a specific
   *           "state".
   *
   * If a button is an object (not an array, not a string), it's assumed that it's a button group,
   * all of it's children will be expected to be buttons or button groups.  A button group is
   * wrapped within a div for styling.  It's important to note that each of the keys, regardless of
   * if it's in a group or not needs to be unique.
   *
   * The save action is special, in that it's handled by Midas directly, all other actions are
   * handled by Midas.Region.
   *
   * Separators are any "button" that's not an array, and are expected to be a string.  You can use
   * three different separator styles: line, spacer, and flex spacer.
   * '-' = line
   * ' ' = spacer
   * '*' = flex spacer
   */
  toolbars: {
    actions: {
      save:                  ['Save', 'Save this page'],
      preview:               ['Preview', 'Preview this page', ['toggle'], ['mode']],
      sep1:                  ' ',
      undoredo: {
        undo:                ['Undo', 'Undo your last action'],
        redo:                ['Redo', 'Redo your last action'],
        sep2:                ' '
        },
      insert: {
        insertlink:          ['Link', 'Insert a hyperlink', ['dialog', '/midas/link']],
        insertmedia:         ['Media', 'Insert media', ['dialog', '/midas/media']],
        inserttable:         ['Table', 'Insert a table', ['dialog', '/midas/table']],
        insertobject:        ['Object', 'Insert an object (form, widget, etc)', ['dialog', '/midas/object']],
        insertcharacter:     ['Character', 'Insert special characters', ['dialog', '/midas/character']],
        inspectorpanel:      ['Inspector', 'Open the element inspector panel', ['panel', '/midas/inspector']],
        sep3:                '*'
        },
      notespanel:            ['Notes', 'Open the page notes panel', ['panel', '/midas/notes']],
      historypanel:          ['History', 'Open the page history panel', ['panel', '/midas/history']]
      },
    htmleditor: {
      style:                 ['Style', '', ['select', '/midas/style.html']],
      formatblock:           ['Block Format', '', ['select', '/midas/formatblock.html']],
      sep1:                  '-',
      //backcolor:             ['Background Color', '', ['palette', '/midas/backcolor.html'], ['context']],
      forecolor:             ['Text Color', '', ['palette', '/midas/forecolor.html'], ['context']],
      sep2:                  '-',
      decoration:            {
        bold:                ['Bold', '', ['context']],
        italic:              ['Italicize', '', ['context']],
        //overline:            ['Overline', '', ['context']],
        strikethrough:       ['Strikethrough', '', ['context']],
        underline:           ['Underline', '', ['context']],
        sep:                 '-'
        },
      script:                {
        subscript:           ['Subscript', '', ['context']],
        superscript:         ['Superscript', '', ['context']],
        sep:                 '-'
        },
      justify:               {
        justifyleft:         ['Align Left', '', ['context']],
        justifycenter:       ['Center', '', ['context']],
        justifyright:        ['Align Right', '', ['context']],
        justifyfull:         ['Justify Full', '', ['context']],
        sep:                 '-'
        },
      list:                  {
        insertunorderedlist: ['Unordered List', '', ['context']],
        insertorderedlist:   ['Numbered List', '', ['context']],
        sep:                 '-'
        },
      indent:                {
        outdent:             ['Decrease Indentation', ''],
        indent:              ['Increase Indentation', ''],
        sep:                 '-'
        },
      //table:                 {
      //  insertrowbefore:     ['Insert Row', 'Insert a table row before'],
      //  insertrowafter:      ['Insert Row', 'Insert a table row after'],
      //  deleterow:           ['Delete Row', 'Delete this table row'],
      //  insertcolumnbefore:  ['Insert Column', 'Insert a table column before'],
      //  insertcolumnafter:   ['Insert Column', 'Insert a table column after'],
      //  deletecolumn:        ['Delete Column', 'Delete this table column'],
      //  sep:                 '-'
      //  },
      breaks:                {
        horizontalrule:      ['Horizontal Rule', ''],
        sep:                 '-'
        },
      removeformatting:      ['Remove Formatting', ''],
      html:                  ['Edit HTML', '', ['dialog', '/midas/html']]
      }
    },

  /* Behaviors
   *
   * Behaviors are used to change the default behaviors of the editor when a given button is
   * clicked.  For example, we prefer to add HR tags using an HR wrapped within a div with a
   * classname of hr, which allows for more flexible styling.  To add your own complex
   * behaviors just prototype them onto Midas.Region.handle.
   *
   * An example behavior would be to add a new button, called buynowbutton, and providing a
   * behavior like:
   *
   * buynowbutton: {insertElement: function() {
   *   return new Element('a', {href: '/buy-now', class: 'buy-now'}).update('Buy Now!');
   * }}
   *
   * It's important to note that the this keyword inside of the callback functions applies to an
   * instance of Midas.Region.
   *
   * Behavior Methods, and expected arguments (arguments can be provided in an array when there
   * is more than one expected):
   *   execCommand: a string of the action to take, or an array [action to take, argument]
   *   insertHTML: a callback function that returns a string
   *   ...
   */
  behaviors: {
    horizontalrule:      {insertHTML: function() {
                            return '

';
                         }}
    }
};
<|endoftext|>


<commit_before>"use strict";
(function() {
  var app = angular.module("gemStore", []);

  app.controller("StoreController", function () {
    this.product = gems;
  });

  app.controller("ReviewController", function () {
    this.review = {};

    this.addReview = function(product) {
      this.review.createdOn = Date.now(); //  returns the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC
      product.reviews.push(this.review);

      this.review = {};
    };
  });

  app.directive("productTitle", function () {
    return {
      restrict: "A",
      templateUrl: "product-title.html"
    }
  });

  app.directive("productPanels", function () {
    return {
      restrict: "E",
      templateUrl: "product-panels.html",
      controller: function () {
        this.tab = 1;

        this.setTab = function (setTab) {
          this.tab = setTab;
        };

        this.isSet = function (setValue) {
          return this.tab === setValue;
        };

        this.setActive = function (activeValue) {
          return this.tab === activeValue;
        };
      },
      controllerAs: "navTab"
    }
  });

  app.directive("productGallery", function () {
    return {
      restrict: "E",
      templateUrl: "product-gallery.html",
      controller: function () {
        this.currentImage = 0;

        this.setCurrent = function (currentValue) {
          this.currentImage = currentValue || 0;
        };

        this.toggleCurrent = function (toggleValue) {
          if (this.currentImage === toggleValue) {
            return false;
          }

          this.currentImage = toggleValue || 0;
        };

        this.setActive = function (activeValue) {
          return this.currentImage === activeValue;
        };
      },
      controllerAs: "gallery"
    }
  });

  var gems = [
    {
      name: 'Azurite',
      description: "Some gems have hidden qualities beyond their luster, beyond their shine... Azurite is one of those gems. Some gems have hidden qualities beyond their luster, beyond their shine... Azurite is one of those gems. Some gems have hidden qualities beyond their luster, beyond their shine... Azurite is one of those gems.",
      shine: 8,
      price: 110.50,
      rarity: 7,
      color: '#CCC',
      faces: 14,
      images: [
        "images/gem-02.gif",
        "images/gem-05.gif",
        "images/gem-09.gif"
      ],
      reviews: [{
        stars: 5,
        body: "I love this gem!",
        author: "joe@example.org",
        createdOn: 1397490980837
      }, {
        stars: 1,
        body: "This gem sucks.",
        author: "tim@example.org",
        createdOn: 1397490980837
      }],
      canPurchase: true
    },
    {
      name: 'Bloodstone',
      description: "Origin of the Bloodstone is unknown, hence its low value. It has a very high shine and 12 sides, however. Origin of the Bloodstone is unknown, hence its low value. It has a very high shine and 12 sides, however. Origin of the Bloodstone is unknown, hence its low value. It has a very high shine and 12 sides, however.",
      shine: 9,
      price: 22.90,
      rarity: 6,
      color: '#EEE',
      faces: 12,
      images: [
        "images/gem-01.gif",
        "images/gem-03.gif",
        "images/gem-04.gif"
      ],
      reviews: [{
        stars: 3,
        body: "I think this gem was just OK, could honestly use more shine, IMO.",
        author: "JimmyDean@example.org",
        createdOn: 1397490980837
      }, {
        stars: 4,
        body: "Any gem with 12 faces is for me!",
        author: "gemsRock@example.org",
        createdOn: 1397490980837
      }],
      canPurchase: true
    },
    {
      name: 'Zircon',
      description: "Zircon is our most coveted and sought after gem. You will pay much to be the proud owner of this gorgeous and high shine gem. Zircon is our most coveted and sought after gem. You will pay much to be the proud owner of this gorgeous and high shine gem. Zircon is our most coveted and sought after gem. You will pay much to be the proud owner of this gorgeous and high shine gem.",
      shine: 70,
      price: 1100,
      rarity: 2,
      color: '#000',
      faces: 6,
      images: [
        "images/gem-06.gif",
        "images/gem-07.gif",
        "images/gem-10.gif"
      ],
      reviews: [{
        stars: 1,
        body: "This gem is WAY too expensive for its rarity value.",
        author: "turtleguyy@example.org",
        createdOn: 1397490980837
      }, {
        stars: 1,
        body: "BBW: High Shine != High Quality.",
        author: "LouisW407@example.org",
        createdOn: 1397490980837
      }, {
        stars: 1,
        body: "Don't waste your rubles!",
        author: "nat@example.org",
        createdOn: 1397490980837
      }],
      canPurchase: true
    }];
})();
<commit_msg>Create custom Directive productButton<commit_after>"use strict";
(function() {
  var app = angular.module("gemStore", []);

  app.controller("StoreController", function () {
    this.product = gems;
  });

  app.controller("ReviewController", function () {
    this.review = {};

    this.addReview = function(product) {
      this.review.createdOn = Date.now(); //  returns the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC
      product.reviews.push(this.review);

      this.review = {};
    };
  });

  app.directive("productTitle", function () {
    return {
      restrict: "A",
      templateUrl: "product-title.html"
    }
  });

  app.directive("productPanels", function () {
    return {
      restrict: "E",
      templateUrl: "product-panels.html",
      controller: function () {
        this.tab = 1;

        this.setTab = function (setTab) {
          this.tab = setTab;
        };

        this.isSet = function (setValue) {
          return this.tab === setValue;
        };

        this.setActive = function (activeValue) {
          return this.tab === activeValue;
        };
      },
      controllerAs: "navTab"
    }
  });

  app.directive("productGallery", function () {
    return {
      restrict: "E",
      templateUrl: "product-gallery.html",
      controller: function () {
        this.currentImage = 0;

        this.setCurrent = function (currentValue) {
          this.currentImage = currentValue || 0;
        };

        this.toggleCurrent = function (toggleValue) {
          if (this.currentImage === toggleValue) {
            return false;
          }

          this.currentImage = toggleValue || 0;
        };

        this.setActive = function (activeValue) {
          return this.currentImage === activeValue;
        };
      },
      controllerAs: "gallery"
    }
  });

  app.directive("productButton", function () {
    return {
      restrict: "E",
      templateUrl: "product-button.html"
    };
  });

  var gems = [
    {
      name: 'Azurite',
      description: "Some gems have hidden qualities beyond their luster, beyond their shine... Azurite is one of those gems. Some gems have hidden qualities beyond their luster, beyond their shine... Azurite is one of those gems. Some gems have hidden qualities beyond their luster, beyond their shine... Azurite is one of those gems.",
      shine: 8,
      price: 110.50,
      rarity: 7,
      color: '#CCC',
      faces: 14,
      images: [
        "images/gem-02.gif",
        "images/gem-05.gif",
        "images/gem-09.gif"
      ],
      reviews: [{
        stars: 5,
        body: "I love this gem!",
        author: "joe@example.org",
        createdOn: 1397490980837
      }, {
        stars: 1,
        body: "This gem sucks.",
        author: "tim@example.org",
        createdOn: 1397490980837
      }],
      canPurchase: true
    },
    {
      name: 'Bloodstone',
      description: "Origin of the Bloodstone is unknown, hence its low value. It has a very high shine and 12 sides, however. Origin of the Bloodstone is unknown, hence its low value. It has a very high shine and 12 sides, however. Origin of the Bloodstone is unknown, hence its low value. It has a very high shine and 12 sides, however.",
      shine: 9,
      price: 22.90,
      rarity: 6,
      color: '#EEE',
      faces: 12,
      images: [
        "images/gem-01.gif",
        "images/gem-03.gif",
        "images/gem-04.gif"
      ],
      reviews: [{
        stars: 3,
        body: "I think this gem was just OK, could honestly use more shine, IMO.",
        author: "JimmyDean@example.org",
        createdOn: 1397490980837
      }, {
        stars: 4,
        body: "Any gem with 12 faces is for me!",
        author: "gemsRock@example.org",
        createdOn: 1397490980837
      }],
      canPurchase: true
    },
    {
      name: 'Zircon',
      description: "Zircon is our most coveted and sought after gem. You will pay much to be the proud owner of this gorgeous and high shine gem. Zircon is our most coveted and sought after gem. You will pay much to be the proud owner of this gorgeous and high shine gem. Zircon is our most coveted and sought after gem. You will pay much to be the proud owner of this gorgeous and high shine gem.",
      shine: 70,
      price: 1100,
      rarity: 2,
      color: '#000',
      faces: 6,
      images: [
        "images/gem-06.gif",
        "images/gem-07.gif",
        "images/gem-10.gif"
      ],
      reviews: [{
        stars: 1,
        body: "This gem is WAY too expensive for its rarity value.",
        author: "turtleguyy@example.org",
        createdOn: 1397490980837
      }, {
        stars: 1,
        body: "BBW: High Shine != High Quality.",
        author: "LouisW407@example.org",
        createdOn: 1397490980837
      }, {
        stars: 1,
        body: "Don't waste your rubles!",
        author: "nat@example.org",
        createdOn: 1397490980837
      }],
      canPurchase: true
    }];
})();
<|endoftext|>


<commit_before>var assert = require('assert');
var request = require('request');
var http = require('http');
var path = require('path');

var Proxy = require('../../src/server');
var testServer = require('./server');

describe('#http server', function () {
  var proxyServer;
  before(function () {
    testServer.listen(61234);

    proxyServer = new Proxy(8850);
    proxyServer.addRewriteFile(path.join(__dirname, 'conf', 'rewrite'));
    proxyServer.start();
    // proxyServer.openBrowser('chrome', '127.0.0.1:8850', false);
  });

  after(function () {
    testServer.close();
    proxyServer.stop();
  });

  describe('#api', function () {
    it('#start()', function (done) {
      var server = new Proxy(8848);
      server.start().then(function () {
        if (server.httpServer instanceof http.Server) {
          done();
        } else {
          done(new Error('server.httpServer is not an instance of http.Server'));
        }
        server.stop();
      });
    });
  });

  describe('#server response', function () {
    it('request /', function (done) {
      request('http://127.0.0.1:8850', function (err, response, body) {
        if (body.indexOf('127.0.0.1:8850/proxy.pac') !== -1) {
          done();
        } else {
          done(err || new Error('Body not match'));
        }
      });
    });

    it('request /proxy.pac', function (done) {
      request('http://127.0.0.1:8850/proxy.pac', function (err, response, body) {
        if (err) {
          done(err);
        }

        assert.equal(response.statusCode, 200);
        // assert.notEqual(body.indexOf('FindProxyForURL'), -1);
        done();
      });
    });

    it('request /favicon.ico', function (done) {
      request('http://127.0.0.1:8850/favicon.ico', function (err, response, body) {
        if (err) {
          done(err);
        }

        assert.equal(response.statusCode, 200);
        assert.equal(response.headers['content-type'], 'application/x-ico');
        done();
      });
    });
  });

  describe('# proxy', function () {
    it('request t.ttt.com/', function (done) {
      request({
        uri: 'http://t.ttt.com/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (body === 'Hello, hiproxy') {
          done();
        } else {
          done(err || new Error('Body not match'));
        }
      });
    });

    it('request t.ttt.com/t/ proxy ok', function (done) {
      request({
        uri: 'http://t.ttt.com/t/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (body === 'GET /test/ OK.') {
          done();
        } else {
          done(err || new Error('Body not match'));
        }
      });
    });

    it('request t.ttt.com/t/ header ok', function (done) {
      request({
        uri: 'http://t.ttt.com/t/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (err) {
          return done(err);
        }

        var headers = response.headers;

        assert.equal('t.ttt.com', headers.host);
        assert.equal('hiproxy', headers.proxy_app);
        assert.equal('1', headers.set_header_field_1);
        assert.deepEqual(['cookie1=c1', 'cookie2=c2'].sort(), headers['set-cookie'].sort());

        done();
      });
    });

    it('request t.ttt.com/source/ (alias, root)', function (done) {
      request({
        uri: 'http://t.ttt.com/source/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (err) {
          return done(err);
        }

        var headers = response.headers;

        assert.equal('
hello, hiproxy
', body);
        assert.equal('text/html', headers['content-type']);
        assert.equal('hiproxy_static', headers.server);

        done();
      });
    });

    it('request t.ttt.com/source/a.json', function (done) {
      request({
        uri: 'http://t.ttt.com/source/a.json',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (err) {
          return done(err);
        }

        var headers = response.headers;

        assert.equal('{"ret": true}', body);
        assert.equal('application/json', headers['content-type']);

        done();
      });
    });
  });

  describe('# commands', function () {
    it('request t.ttt.com/', function (done) {
      request({
        uri: 'http://t.ttt.com/t/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        var headers = response.headers;
        var setHeaderField1 = headers['set_header_field_1'];

        if (setHeaderField1 === '1') {
          done();
        } else {
          done(err || new Error('`set-header` result not right'));
        }
      });
    });

    it('request t.ttt.com/', function (done) {
      request({
        uri: 'http://t.ttt.com/source/b.json',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (response.statusCode === 404) {
          done();
        } else {
          done(err || new Error('response status not right'));
        }
      });
    });

    it('should get var value rightly(location > domain > global)', function (done) {
      request({
        uri: 'http://t.ttt.com/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        assert.equal(err, null);
        assert.equal(response.headers.str, 'str_domain_scope, str_location_scope');

        done();
      });
    });
  });

  describe('#Random PORT', function () {
    it('Should choose a random available port when use `(0, 0, dir)`', function (done) {
      var server = new Proxy(0, 0, process.cwd());
      server.start().then(function () {
        if (server.httpPort > 0 && server.httpsPort > 0) {
          done();
        } else {
          done(new Error('Server port not right.'));
        }
        server.stop();
      });
    });

    it('Should not choose a random available port when use `undefined` or `null`', function (done) {
      var server = new Proxy(0, undefined, process.cwd());
      server.start().then(function () {
        if (server.httpPort > 0 && Number(server.httpsPort) === 0) {
          done();
        } else {
          done(new Error('Server port not right.'));
        }
        server.stop();
      });
    });

    it('Should use user-specified port', function (done) {
      var server = new Proxy(6688, 9900, process.cwd());
      server.start().then(function () {
        if (server.httpPort === 6688 && server.httpsPort === 9900) {
          done();
        } else {
          done(new Error('Server port not right.'));
        }
        server.stop();
      });
    });

    it('Should use user-specified port(use object)', function (done) {
      var server = new Proxy({httpPort: 6699, httpsPort: 9911, dir: process.cwd()});
      server.start().then(function () {
        if (server.httpPort === 6699 && server.httpsPort === 9911) {
          done();
        } else {
          done(new Error('Server port not right.'));
        }
        server.stop();
      });
    });

    it('Should not choose a random available port when use `undefined` or `null`(use object)', function (done) {
      var server = new Proxy({httpPort: undefined, httpsPort: null, dir: process.cwd()});
      server.start().then(function () {
        if (server.httpPort > 0 && Number(server.httpsPort) === 0) {
          done();
        } else {
          done(new Error('Server port not right.'));
        }
        server.stop();
      });
    });
  });
});
<commit_msg>add new test case<commit_after>var assert = require('assert');
var request = require('request');
var http = require('http');
var path = require('path');

var Proxy = require('../../src/server');
var testServer = require('./server');

describe('#http server', function () {
  var proxyServer;
  before(function () {
    testServer.listen(61234);

    proxyServer = new Proxy(8850);
    proxyServer.addRewriteFile(path.join(__dirname, 'conf', 'rewrite'));
    proxyServer.start();
    // proxyServer.openBrowser('chrome', '127.0.0.1:8850', false);
  });

  after(function () {
    testServer.close();
    proxyServer.stop();
  });

  describe('#api', function () {
    it('#start()', function (done) {
      var server = new Proxy(8848);
      server.start().then(function () {
        if (server.httpServer instanceof http.Server) {
          done();
        } else {
          done(new Error('server.httpServer is not an instance of http.Server'));
        }
        server.stop();
      });
    });
  });

  describe('#server response', function () {
    it('request /', function (done) {
      request('http://127.0.0.1:8850', function (err, response, body) {
        if (body.indexOf('127.0.0.1:8850/proxy.pac') !== -1) {
          done();
        } else {
          done(err || new Error('Body not match'));
        }
      });
    });

    it('request /proxy.pac', function (done) {
      request('http://127.0.0.1:8850/proxy.pac', function (err, response, body) {
        if (err) {
          done(err);
        }

        assert.equal(response.statusCode, 200);
        // assert.notEqual(body.indexOf('FindProxyForURL'), -1);
        done();
      });
    });

    it('request /favicon.ico', function (done) {
      request('http://127.0.0.1:8850/favicon.ico', function (err, response, body) {
        if (err) {
          done(err);
        }

        assert.equal(response.statusCode, 200);
        assert.equal(response.headers['content-type'], 'application/x-ico');
        done();
      });
    });
  });

  describe('# proxy', function () {
    it('request t.ttt.com/', function (done) {
      request({
        uri: 'http://t.ttt.com/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (body === 'Hello, hiproxy') {
          done();
        } else {
          done(err || new Error('Body not match'));
        }
      });
    });

    it('request t.ttt.com/t/ proxy ok', function (done) {
      request({
        uri: 'http://t.ttt.com/t/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (body === 'GET /test/ OK.') {
          done();
        } else {
          done(err || new Error('Body not match'));
        }
      });
    });

    it('request t.ttt.com/t/ header ok', function (done) {
      request({
        uri: 'http://t.ttt.com/t/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (err) {
          return done(err);
        }

        var headers = response.headers;

        assert.equal('t.ttt.com', headers.host);
        assert.equal('hiproxy', headers.proxy_app);
        assert.equal('1', headers.set_header_field_1);
        assert.deepEqual(['cookie1=c1', 'cookie2=c2'].sort(), headers['set-cookie'].sort());

        done();
      });
    });

    it('request t.ttt.com/source/ (alias, root)', function (done) {
      request({
        uri: 'http://t.ttt.com/source/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (err) {
          return done(err);
        }

        var headers = response.headers;

        assert.equal('
hello, hiproxy
', body);
        assert.equal('text/html', headers['content-type']);
        assert.equal('hiproxy_static', headers.server);

        done();
      });
    });

    it('request t.ttt.com/source/a.json', function (done) {
      request({
        uri: 'http://t.ttt.com/source/a.json',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (err) {
          return done(err);
        }

        var headers = response.headers;

        assert.equal('{"ret": true}', body);
        assert.equal('application/json', headers['content-type']);

        done();
      });
    });
  });

  describe('# commands', function () {
    it('request t.ttt.com/', function (done) {
      request({
        uri: 'http://t.ttt.com/t/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        var headers = response.headers;
        var setHeaderField1 = headers['set_header_field_1'];

        if (setHeaderField1 === '1') {
          done();
        } else {
          done(err || new Error('`set-header` result not right'));
        }
      });
    });

    it('request t.ttt.com/', function (done) {
      request({
        uri: 'http://t.ttt.com/source/b.json',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        if (response.statusCode === 404) {
          done();
        } else {
          done(err || new Error('response status not right'));
        }
      });
    });

    it('should get var value rightly(location > domain > global)', function (done) {
      request({
        uri: 'http://t.ttt.com/',
        proxy: 'http://127.0.0.1:8850'
      }, function (err, response, body) {
        assert.equal(err, null);
        assert.equal(response.headers.str, 'str_domain_scope, str_location_scope');

        done();
      });
    });
  });

  describe('#Random PORT', function () {
    it('Should choose a random available port when use `(0, 0, dir)`', function (done) {
      var server = new Proxy(0, 0, process.cwd());
      server.start().then(function () {
        if (server.httpPort > 0 && server.httpsPort > 0) {
          done();
        } else {
          done(new Error('Server port not right.'));
        }
        server.stop();
      });
    });

    it('Should not choose a random available port when use `undefined` or `null`', function (done) {
      var server = new Proxy(0, undefined, process.cwd());
      server.start().then(function () {
        if (server.httpPort > 0 && Number(server.httpsPort) === 0) {
          done();
        } else {
          done(new Error('Server port not right.'));
        }
        server.stop();
      });
    });

    it('Should use user-specified port', function (done) {
      var server = new Proxy(6688, 9900, process.cwd());
      server.start().then(function () {
        if (server.httpPort === 6688 && server.httpsPort === 9900) {
          done();
        } else {
          done(new Error('Server port not right.'));
        }
        server.stop();
      });
    });

    it('Should use user-specified port(use object)', function (done) {
      var server = new Proxy({httpPort: 6699, httpsPort: 9911, dir: process.cwd()});
      server.start().then(function () {
        if (server.httpPort === 6699 && server.httpsPort === 9911) {
          done();
        } else {
          done(new Error('Server port not right.'));
        }
        server.stop();
      });
    });

    it('Should not choose a random available port when use `undefined` or `null`(use object)', function (done) {
      var server = new Proxy({httpPort: undefined, httpsPort: null, dir: process.cwd()});
      server.start().then(function () {
        if (server.httpPort > 0 && Number(server.httpsPort) === 0) {
          done();
        } else {
          done(new Error('Server port not right.'));
        }
        server.stop();
      });
    });

    it('Should choose random prot when has no params.', function (done) {
      var server = new Proxy();
      server.start().then(function () {
        if (server.httpPort > 0 && Number(server.httpsPort) === 0) {
          done();
        } else {
          done(new Error('Server port not right.'));
        }
        server.stop();
      });
    });
  });
});
<|endoftext|>


<commit_before>const isStaging = () => process.env.SCRATCH_ENV === 'staging';

const flagInUrl = flag => {
    const url = (window.location && window.location.search) || '';
    return url.indexOf(`${flag}=true`) !== -1;
};

module.exports = {
    CHROME_APP_RELEASED: isStaging() && flagInUrl('CHROME_APP_RELEASED')
};
<commit_msg>Enable chrome app feature flag<commit_after>// eslint-disable-next-line no-unused-vars
const isStaging = () => process.env.SCRATCH_ENV === 'staging';

// eslint-disable-next-line no-unused-vars
const flagInUrl = flag => {
    const url = (window.location && window.location.search) || '';
    return url.indexOf(`${flag}=true`) !== -1;
};

module.exports = {
    CHROME_APP_RELEASED: true
};
<|endoftext|>


<commit_before>if (!Midas) var Midas = {};
Midas.Dialog = Class.create({
  version: 0.2,
  button: null,
  element: null,
  setupFunction: null,
  options: {
    url: null,
    configuration: null
  },

  initialize: function(button, name, toolbar, options) {
    if (!Midas.version) throw('Midas.Dialog requires Midas');

    this.button = button;
    this.name = name;
    this.toolbar = toolbar;

    this.options = Object.extend(Object.clone(this.options), options);
    this.options['configuration'] = this.options['configuration'] || Midas.Config;
    this.config = this.options['configuration'];

    this.build();
    this.setupObservers();
  },

  build: function() {
    this.element = new Element('div', {'class': 'midas-dialog', style: 'display:none'});
    this.toolbar.element.appendChild(this.element);
  },

  setupObservers: function() {
    Event.observe(window, 'resize', function() {
      this.position();
    }.bind(this));
    Event.observe(this.element, 'mousedown', function(event) {
      event.stop();
    });
    Event.observe(this.button, 'click', function() {
      if (!this.element || this.disabled()) return;
      if (this.visible()) this.hide();
      else this.show();
    }.bind(this));
  },

  show: function() {
    if (!this.loaded) {
      this.position(true);
      this.load(this.show.bind(this));
      return;
    }

    if (this.toolbar.activeRegion) {
      this.contextClass = this.toolbar.activeRegion.name;
      this.element.addClassName(this.contextClass);
    }

    this.element.setStyle({width: 'auto', height: 'auto'});
    this.position();
    this.element.show();
  },

  hide: function() {
    if (this.contextClass) {
      this.element.removeClassName(this.contextClass);
      this.contextClass = null;
    }

    this.element.hide();
  },

  position: function(keepVisible) {
  },

  visible: function() {
    return (!this.element || this.element.getStyle('display') == 'block');
  },

  disabled: function() {
    return (this.button.hasClassName('disabled') || this.button.up('.disabled'));
  },

  load: function(callback) {
    new Ajax.Request(this.options.url, {
      method: 'get',
      onSuccess: function(transport) {
        this.loaded = true;
        this.element.removeClassName('loading');
        this.element.innerHTML = transport.responseText;
        transport.responseText.evalScripts();

        this.setupFunction = window['midas_setup_' + this.name];
        if (this.setupFunction) this.setupFunction.call(this);

        if (callback) callback();
      }.bind(this),
      onFailure: function() {
        this.hide();
        alert('Midas was unable to load "' + this.options.url + '" for the "' + this.name + '" dialog');
      }.bind(this)
    });
  },

  execute: function(action, options, event) {
    Midas.fire('button', {action: this.name, event: event, toolbar: this.toolbar, options: options});
  },

  destroy: function() {
    if (this.element) this.element.remove();
    this.element = null;
  }
});
<commit_msg>initial animation attempt on dialogs<commit_after>if (!Midas) var Midas = {};
Midas.Dialog = Class.create({
  version: 0.2,
  button: null,
  element: null,
  setupFunction: null,
  options: {
    url: null,
    configuration: null
  },

  initialize: function(button, name, toolbar, options) {
    if (!Midas.version) throw('Midas.Dialog requires Midas');

    this.button = button;
    this.name = name;
    this.toolbar = toolbar;

    this.options = Object.extend(Object.clone(this.options), options);
    this.options['configuration'] = this.options['configuration'] || Midas.Config;
    this.config = this.options['configuration'];

    this.build();
    this.setupObservers();
  },

  build: function() {
    this.element = new Element('div', {'class': 'midas-dialog', style: 'display:none'});
    this.toolbar.element.appendChild(this.element);
  },

  setupObservers: function() {
    Event.observe(window, 'resize', function() {
      this.position();
    }.bind(this));
    Event.observe(this.element, 'mousedown', function(event) {
      event.stop();
    });
    Event.observe(this.button, 'click', function() {
      if (!this.element || this.disabled()) return;
      if (this.visible()) this.hide();
      else this.show();
    }.bind(this));
  },

  show: function() {
    if (!this.loaded) {
      this.position(true);
      this.load(this.show.bind(this));
      return;
    }

    if (this.toolbar.activeRegion) {
      this.contextClass = this.toolbar.activeRegion.name;
      this.element.addClassName(this.contextClass);
    }

    this.element.setStyle({width: 'auto', height: 'auto'});
    this.position();

//    new Effect.Appear(this.element, {
//      queue: {scope: 'dialog', limit: 2},
//      transition: Effect.Transitions.sinoidal,
//      duration: 1
//    });
  },

  hide: function() {
    if (this.contextClass) {
      this.element.removeClassName(this.contextClass);
      this.contextClass = null;
    }

    this.element.hide();
  },

  position: function(keepVisible) {
  },

  visible: function() {
    return (!this.element || this.element.getStyle('display') == 'block');
  },

  disabled: function() {
    return (this.button.hasClassName('disabled') || this.button.up('.disabled'));
  },

  load: function(callback) {
    new Ajax.Request(this.options.url, {
      method: 'get',
      onSuccess: function(transport) {
        this.loaded = true;
        this.element.removeClassName('loading');
        this.element.innerHTML = transport.responseText;
        transport.responseText.evalScripts();

        this.setupFunction = window['midas_setup_' + this.name];
        if (this.setupFunction) this.setupFunction.call(this);

        if (callback) callback();
      }.bind(this),
      onFailure: function() {
        this.hide();
        alert('Midas was unable to load "' + this.options.url + '" for the "' + this.name + '" dialog');
      }.bind(this)
    });
  },

  execute: function(action, options, event) {
    Midas.fire('button', {action: this.name, event: event, toolbar: this.toolbar, options: options});
  },

  destroy: function() {
    if (this.element) this.element.remove();
    this.element = null;
  }
});
<|endoftext|>


<commit_before>/**
 Coupley entry point for webpack
 */

//es6 imports
import React from 'react';
import ReactDOM from 'react-dom';
//Router components
import { Router, Route, Link, hashHistory } from 'react-router';

import Header from './components/base/Header.react';
import Home from './components/Home.react';
import Profile from './components/profile/profile.react';
import ActivityContainer from './components/profile/ActivityFeed/ActivityFeedContainer.react';
import About from './components/profile/About.react';
import Photos from './components/profile/Photos.react';
import Search from './components/Search.react';
import Admin from './components/admin/dashboard.react';

ReactDOM.render((
  <Router history={hashHistory}>
    <Route path="/login" component={Home} />
    <Route path="/dashboard" component={Admin} />
    <Route path="/" component={Header}>
      <Route path="/search" component={Search} />
      <Route path="profile" component={Profile} >
      	<Route path="activityfeed" component={ActivityContainer} />
      	<Route path="about" component={About} />
        <Route path="photos" component={Photos} />
      </Route>
    </Route>
  </Router>
  ),
  document.getElementById('content')
);
<commit_msg>:zap: Add onEnter to react-router<commit_after>/**
 Coupley entry point for webpack
 */

//es6 imports
import React from 'react';
import ReactDOM from 'react-dom';
//Router components
import { Router, Route, Link, hashHistory } from 'react-router';

import Header from './components/base/Header.react';
import Home from './components/Home.react';
import Profile from './components/profile/profile.react';
import ActivityContainer from './components/profile/ActivityFeed/ActivityFeedContainer.react';
import About from './components/profile/About.react';
import Photos from './components/profile/Photos.react';
import Search from './components/Search.react';
import Admin from './components/admin/dashboard.react';

function requireAuth(nextState, replace) {
  if(! localStorage.getItem('apitoken')) {
    replace({
      pathname: '/login',
      state: { nextPathname: nextState.location.pathname }
    })
  }
}

ReactDOM.render((
  <Router history={hashHistory}>
    <Route path="/login" component={Home} />
    <Route path="/dashboard" component={Admin} />
    <Route path="/" component={Header} onEnter={requireAuth} >
      <Route path="/search" component={Search} />
      <Route path="profile" component={Profile} >
      	<Route path="activityfeed" component={ActivityContainer} />
      	<Route path="about" component={About} />
        <Route path="photos" component={Photos} />
      </Route>
    </Route>
  </Router>
  ),
  document.getElementById('content')
);
<|endoftext|>


<commit_before>var chai = require('chai'),
    assert = chai.assert,
    Reflux = require('../src'),
    sinon = require('sinon');

describe("the publisher methods",function(){
	var pub = Reflux.publisherMethods;
	describe("the listen method",function(){
		var emitter = {
				addListener:sinon.spy(),
				removeListener:sinon.spy()
			},
			context = {
				emitter: emitter,
				eventLabel: "LABEL",
			},
			callback = sinon.spy(),
			cbcontext = {foo:"BAR"},
			result = pub.listen.call(context,callback,cbcontext);
		it("should call addListener correctly",function(){
			var args = emitter.addListener.firstCall.args;
			assert.equal(args[0],context.eventLabel);
			assert.isFunction(args[1]);
			args[1](["ARG1","ARG2"]);
			assert.deepEqual(callback.firstCall.args,["ARG1","ARG2"]);
			assert.equal(callback.firstCall.thisValue,cbcontext);
		});
		describe("the returned value",function(){
			it("should be a function",function(){
				assert.isFunction(result);
			});
			it("should remove the listener correctly",function(){
				result();
				assert.deepEqual(emitter.removeListener.firstCall.args,[context.eventLabel,emitter.addListener.firstCall.args[1]]);
			});
		})
	});
	describe("the trigger method",function(){
		describe("when shouldEmit returns true",function(){
			var emitter = {
					emit: sinon.spy()
				},
				context = {
					eventLabel: "LABEL",
					preEmit:sinon.spy(),
					shouldEmit:sinon.stub().returns(true),
					emitter: emitter
				};
			pub.trigger.call(context,"FOO","BAR");
			it("should call preEmit correctly",function(){
				assert.deepEqual(context.preEmit.firstCall.args,["FOO","BAR"]);
			});
			it("should call shouldEmit correctly",function(){
				assert.deepEqual(context.shouldEmit.firstCall.args,["FOO","BAR"]);
			});
			it("should call emit on the emitter",function(){
				// args are weird because it is an arguments object
				var args = emitter.emit.firstCall.args;
				assert.deepEqual(args[0],"LABEL");
				assert.deepEqual(args[1][0],"FOO");
				assert.deepEqual(args[1][1],"BAR");
			});
		});
		describe("when shouldEmit returns false",function(){
			var emitter = {
					emit: sinon.spy()
				},
				context = {
					preEmit:sinon.spy(),
					shouldEmit:sinon.stub().returns(false),
					emitter: emitter
				};
			pub.trigger.call(context,"FOO","BAR");
			it("should not emit anything",function(){
				assert.equal(emitter.emit.callCount,0);
			});
		});
	});
});<commit_msg>added missing semicolon (sorry travis)<commit_after>var chai = require('chai'),
    assert = chai.assert,
    Reflux = require('../src'),
    sinon = require('sinon');

describe("the publisher methods",function(){
	var pub = Reflux.publisherMethods;
	describe("the listen method",function(){
		var emitter = {
				addListener:sinon.spy(),
				removeListener:sinon.spy()
			},
			context = {
				emitter: emitter,
				eventLabel: "LABEL",
			},
			callback = sinon.spy(),
			cbcontext = {foo:"BAR"},
			result = pub.listen.call(context,callback,cbcontext);
		it("should call addListener correctly",function(){
			var args = emitter.addListener.firstCall.args;
			assert.equal(args[0],context.eventLabel);
			assert.isFunction(args[1]);
			args[1](["ARG1","ARG2"]);
			assert.deepEqual(callback.firstCall.args,["ARG1","ARG2"]);
			assert.equal(callback.firstCall.thisValue,cbcontext);
		});
		describe("the returned value",function(){
			it("should be a function",function(){
				assert.isFunction(result);
			});
			it("should remove the listener correctly",function(){
				result();
				assert.deepEqual(emitter.removeListener.firstCall.args,[context.eventLabel,emitter.addListener.firstCall.args[1]]);
			});
		});
	});
	describe("the trigger method",function(){
		describe("when shouldEmit returns true",function(){
			var emitter = {
					emit: sinon.spy()
				},
				context = {
					eventLabel: "LABEL",
					preEmit:sinon.spy(),
					shouldEmit:sinon.stub().returns(true),
					emitter: emitter
				};
			pub.trigger.call(context,"FOO","BAR");
			it("should call preEmit correctly",function(){
				assert.deepEqual(context.preEmit.firstCall.args,["FOO","BAR"]);
			});
			it("should call shouldEmit correctly",function(){
				assert.deepEqual(context.shouldEmit.firstCall.args,["FOO","BAR"]);
			});
			it("should call emit on the emitter",function(){
				// args are weird because it is an arguments object
				var args = emitter.emit.firstCall.args;
				assert.deepEqual(args[0],"LABEL");
				assert.deepEqual(args[1][0],"FOO");
				assert.deepEqual(args[1][1],"BAR");
			});
		});
		describe("when shouldEmit returns false",function(){
			var emitter = {
					emit: sinon.spy()
				},
				context = {
					preEmit:sinon.spy(),
					shouldEmit:sinon.stub().returns(false),
					emitter: emitter
				};
			pub.trigger.call(context,"FOO","BAR");
			it("should not emit anything",function(){
				assert.equal(emitter.emit.callCount,0);
			});
		});
	});
});<|endoftext|>


<commit_before>/**
 * Provides several methods that override native Element-methods to work with the vdom.
 *
 *
 * Copyright (c) 2014 ITSA - https://github.com/itsa
 *

 * New BSD License - http://choosealicense.com/licenses/bsd-3-clause/
 *
 * @module vdom
 * @submodule extend-element
 * @class Element
 * @since 0.0.1
*/

/*
* attributes:
* value, expanded, invalid-value
*/

require('polyfill/polyfill-base.js');
require('js-ext/lib/string.js');
require('css');
require('./css/i-select.css');

var utils = require('utils'),
    asyncSilent = utils.asyncSilent,
    laterSilent = utils.laterSilent,
    DELAY_BLURCLOSE = 125,
    SUPPRESS_DELAY = 175;

module.exports = function (window) {
    "use strict";

    var DEFAULT_INVALID_VALUE = 'choose',
        itagName = 'i-select',
        DOCUMENT = window.document,
        itagCore = require('itags.core')(window),
        HIDDEN = 'itsa-hidden',
        SHOW = 'i-select-show',
        Event, Itag, IFormElement;

    if (!window.ITAGS[itagName]) {
        Event = require('event-mobile')(window);
        require('event-dom/extra/blurnode.js')(window);
        require('focusmanager')(window);
        require('i-item')(window);
        require('i-head')(window);

        IFormElement = require('i-formelement')(window);

        Event.before(itagName+':manualfocus', function(e) {
            // the i-select itself is unfocussable, but its button is
            // we need to patch `manualfocus`,
            // which is emitted on node.focus()
            // a focus by userinteraction will always appear on the button itself
            // so we don't bother that
            var element = e.target;
            e.preventDefault();
            element.itagReady().then(
                function() {
                    var button = element.getElement('button');
                    button && button.focus(true);
                }
            );
        });

        Event.before('keydown', function(e) {
            if (e.keyCode===40) {
                // prevent minus windowscroll:
                e.preventDefaultContinue();
            }
        }, 'i-select > button');

        Event.after(['tap', 'keydown'], function(e) {
            var element = e.target.getParent(),
                e_type = e.type,
                model, ulNode, liNode, inactive;
            if ((e_type==='tap') || (e.keyCode===40)) {
                model = element.model;
                if (e.keyCode===40) {
                    e.preventDefault();
                    model.expanded = true;
                }
                else {
                    inactive = element.hasData('_suppressClose');
                    if (inactive) {
                        console.warn('not reacting to '+e_type+'-event: button is in pauzed state');
                        return;
                    }
                    model.expanded = !model.expanded;
                    if (!model.expanded) {
                        liNode = element.getElement('ul[fm-manage] >li[fm-defaultitem]');
                        liNode && liNode.focus(true);
                    }
                }
                if (model.expanded) {
                    ulNode = element.getElement('ul[fm-manage]');
                    ulNode && ulNode.focus(true);
                }
                if (model.expanded || (e_type==='tap')) {
                    element.setData('_suppressClose', true);
                    laterSilent(function() {
                        element.removeData('_suppressClose');
                    }, SUPPRESS_DELAY);
                    ulNode = element.getElement('ul[fm-manage]');
                    ulNode && ulNode.focus(true);
                }
            }
        }, 'i-select > button');

        Event.after(['tap', 'keypress'], function(e) {
            var liNode = e.target,
                e_type = e.type,
                element, index, ulNode, model, inactive;
            if ((e_type==='tap') || (e.keyCode===13)) {
                element = liNode.inside('i-select');
                model = element.model;
                // check for model.expanded --> a hidden selectbox might react on an enterpress
                if (model.expanded) {
                    inactive = element.hasData('_suppressClose');
                    if (inactive) {
                        console.warn('not reacting to '+e_type+'-event: button is in pauzed state');
                        return;
                    }
                    model = element.model;
                    ulNode = liNode.getParent();
                    index = ulNode.getAll('li').indexOf(liNode);
                    model.expanded = false;
                    model.value = index+1;
                    if (e_type==='tap') {
                        element.setData('_suppressClose', true);
                        laterSilent(function() {
                            element.removeData('_suppressClose');
                        }, SUPPRESS_DELAY);
                        ulNode = element.getElement('ul[fm-manage]');
                        ulNode && ulNode.focus(true);
                    }
                    // prevent that the focus will be reset to the focusmanager
                    // when re-synced --> we want the focus on the button:
                    ulNode.removeClass('focussed');
                    asyncSilent(function() {
                        element.focus(true);
                    });
                }
            }
        }, 'i-select ul[fm-manage] > li');

        Event.defineEvent(itagName+':valuechange')
             .unPreventable()
             .noRender();

        Event.after(itagName+':change', function(e) {
            var element = e.target,
                prevValue = element.getData('i-select-value'),
                model = element.model,
                newValue = model.value,
                markValue;
            if (prevValue!==newValue) {
                markValue = newValue - 1;

                /**
                * Emitted when a the i-select changes its value
                *
                * @event i-select:valuechange
                * @param e {Object} eventobject including:
                * @param e.target {HtmlElement} the i-select element
                * @param e.prevValue {Number} the selected item, starting with 1
                * @param e.newValue {Number} the selected item, starting with 1
                * @param e.buttonText {String} the text that will appear on the button
                * @param e.listText {String} the text as it is in the list
                * @since 0.1
                */
                element.emit('valuechange', {
                    prevValue: prevValue,
                    newValue: newValue,
                    buttonText: model.buttonTexts[markValue] || model.items[markValue],
                    listText: model.items[markValue]
                });
            }
            element.setData('i-select-value', newValue);
        });

        Itag = IFormElement.subClass(itagName, {
            /*
             *
             * @property attrs
             * @type Object
             * @since 0.0.1
            */
            attrs: {
                expanded: 'boolean',
                value: 'string',
                prop: 'string',
                'invalid-value': 'string'
            },

           /**
            * Redefines the childNodes of both the vnode as well as its related dom-node. The new
            * definition replaces any previous nodes. (without touching unmodified nodes).
            *
            * Syncs the new vnode's childNodes with the dom.
            *
            * @method _setChildNodes
            * @param newVChildNodes {Array} array with vnodes which represent the new childNodes
            * @private
            * @chainable
            * @since 0.0.1
            */
            init: function() {
                var element = this,
                    itemNodes = element.getAll('>i-item'),
                    items = [],
                    buttonTexts = [],
                    content;
                itemNodes.forEach(function(node, i) {
                    var header = node.getElement('i-head');
                    if (header) {
                        buttonTexts[i] = header.getHTML();
                        header.remove(true);
                    }
                    items[items.length] = node.getHTML();
                });

                element.defineWhenUndefined('items', items)
                       .defineWhenUndefined('buttonTexts', buttonTexts);

                // store its current value, so that valueChange-event can fire:
                element.setData('i-select-value', element.model.value);

                // building the template of the itag:
                content = '<button>

</button>';
                // first: outerdiv which will be relative positioned
                // next: innerdiv which will be absolute positioned
                // also: hide the container by default --> updateUI could make it shown
                content += '
' +
                             '
'+
                               '

';
                             '
'+
                           '
';

                element.setupEvents();
                // set the content:
                element.setHTML(content);
            },

            setupEvents: function() {
                var element = this;
                Event.after('tapoutside', function(e) {
                    // at the end of the eventstack: give `tapoutside` a way to set the '_suppressClose'-data when needed
                    // just async will do
                    asyncSilent(function() {
                        if (!element.hasData('_suppressClose') && !element.contains(e.sourceTarget)) {
                            element.model.expanded = false;
                            DOCUMENT.refreshItags();
                        }
                    });
                }, 'i-select');
                element.selfAfter('blurnode', function() {
                    // at the end of the eventstack: give `blurnode` a way to set the '_suppressClose'-data when needed
                    // need a bit more time because there is time inbetween the blur vs click events
                    laterSilent(function() {
                        if (!element.hasData('_suppressClose')) {
                            element.model.expanded = false;
                            DOCUMENT.refreshItags();
                        }
                    }, DELAY_BLURCLOSE);
                });
            },

           /**
            * Redefines the childNodes of both the vnode as well as its related dom-node. The new
            * definition replaces any previous nodes. (without touching unmodified nodes).
            *
            * Syncs the new vnode's childNodes with the dom.
            *
            * @method _setChildNodes
            * @param newVChildNodes {Array} array with vnodes which represent the new childNodes
            * @private
            * @chainable
            * @since 0.0.1
            */
            sync: function() {
console.warn('syncing i-select');
                // inside sync, YOU CANNOT change attributes which are part of `attrs` !!!
                // those actions will be ignored.

                // BE CAREFUL to start async actions here:
                // be aware that before ending, this method can run again
                // if you do, then make sure to handle possible running
                // async actions well !!!
                var element = this,
                    model = element.model,
                    items = model.items,
                    buttonTexts = model.buttonTexts,
                    value = model.value,
                    item, content, buttonText, len, i, markValue,
                    button, container, itemsContainer, hiddenTimer;

                len = items.length;
                (value>len) && (value=0);
                markValue = value - 1;

                if (value>0) {
                    buttonText = buttonTexts[markValue] || items[markValue];
                }
                else {
                    buttonText = model['invalid-value'] || DEFAULT_INVALID_VALUE;
                }

                // rebuild the button:
                button = element.getElement('button');
                button.getElement('div.btntext').setHTML(buttonText);

                container = element.getElement('>div');

                if (model.expanded) {
                    hiddenTimer = container.getData('_hiddenTimer');
                    hiddenTimer && hiddenTimer.cancel();
                    container.setClass(SHOW);
                    container.removeClass(HIDDEN);
                }
                else {
                    container.removeClass(SHOW);
                    // hide the layer completely: we need to access anything underneath:
                    hiddenTimer = laterSilent(function() {
                        container.setClass(HIDDEN);
                    }, 110);
                    container.setData('_hiddenTimer', hiddenTimer);
                }

                itemsContainer = element.getElement('ul[fm-manage]');
                content = '';
                for (i=0; i<len; i++) {
                    item = items[i];
                    content += '
'+item+'
';
                }

                // set the items:
                itemsContainer.setHTML(content, true);
            }
        });

        itagCore.setDirectEventResponse(Itag, ['keypress', 'keydown']);

        window.ITAGS[itagName] = Itag;
    }

    return window.ITAGS[itagName];
};<commit_msg>intermediate saving<commit_after>/**
 * Provides several methods that override native Element-methods to work with the vdom.
 *
 *
 * Copyright (c) 2014 ITSA - https://github.com/itsa
 *

 * New BSD License - http://choosealicense.com/licenses/bsd-3-clause/
 *
 * @module vdom
 * @submodule extend-element
 * @class Element
 * @since 0.0.1
*/

/*
* attributes:
* value, expanded, invalid-value
*/

require('polyfill/polyfill-base.js');
require('js-ext/lib/string.js');
require('css');
require('./css/i-select.css');

var utils = require('utils'),
    asyncSilent = utils.asyncSilent,
    laterSilent = utils.laterSilent,
    DELAY_BLURCLOSE = 125,
    SUPPRESS_DELAY = 175;

module.exports = function (window) {
    "use strict";

    var DEFAULT_INVALID_VALUE = 'choose',
        itagName = 'i-select',
        DOCUMENT = window.document,
        itagCore = require('itags.core')(window),
        HIDDEN = 'itsa-hidden',
        SHOW = 'i-select-show',
        Event, Itag, IFormElement;

    if (!window.ITAGS[itagName]) {
        Event = require('event-mobile')(window);
        require('event-dom/extra/blurnode.js')(window);
        require('focusmanager')(window);
        require('i-item')(window);
        require('i-head')(window);

        IFormElement = require('i-formelement')(window);

        Event.before(itagName+':manualfocus', function(e) {
            // the i-select itself is unfocussable, but its button is
            // we need to patch `manualfocus`,
            // which is emitted on node.focus()
            // a focus by userinteraction will always appear on the button itself
            // so we don't bother that
            var element = e.target;
            e.preventDefault();
            element.itagReady().then(
                function() {
                    var button = element.getElement('button');
                    button && button.focus(true, true);
                }
            );
        });

        Event.before('keydown', function(e) {
            if (e.keyCode===40) {
                // prevent minus windowscroll:
                e.preventDefaultContinue();
            }
        }, 'i-select > button');

        Event.after(['tap', 'keydown'], function(e) {
            var element = e.target.getParent(),
                e_type = e.type,
                model, ulNode, liNode, inactive;
            if ((e_type==='tap') || (e.keyCode===40)) {
                model = element.model;
                if (e.keyCode===40) {
                    e.preventDefault();
                    model.expanded = true;
                }
                else {
                    inactive = element.hasData('_suppressClose');
                    if (inactive) {
                        console.warn('not reacting to '+e_type+'-event: button is in pauzed state');
                        return;
                    }
                    model.expanded = !model.expanded;
                    if (!model.expanded) {
                        liNode = element.getElement('ul[fm-manage] >li[fm-defaultitem]');
                        liNode && liNode.focus(true);
                    }
                }
                if (model.expanded) {
                    ulNode = element.getElement('ul[fm-manage]');
                    ulNode && ulNode.focus(true);
                }
                if (model.expanded || (e_type==='tap')) {
                    element.setData('_suppressClose', true);
                    laterSilent(function() {
                        element.removeData('_suppressClose');
                    }, SUPPRESS_DELAY);
                    ulNode = element.getElement('ul[fm-manage]');
                    ulNode && ulNode.focus(true);
                }
            }
        }, 'i-select > button');

        Event.after(['tap', 'keypress'], function(e) {
            var liNode = e.target,
                e_type = e.type,
                element, index, ulNode, model, inactive;
            if ((e_type==='tap') || (e.keyCode===13)) {
                element = liNode.inside('i-select');
                model = element.model;
                // check for model.expanded --> a hidden selectbox might react on an enterpress
                if (model.expanded) {
                    inactive = element.hasData('_suppressClose');
                    if (inactive) {
                        console.warn('not reacting to '+e_type+'-event: button is in pauzed state');
                        return;
                    }
                    model = element.model;
                    ulNode = liNode.getParent();
                    index = ulNode.getAll('li').indexOf(liNode);
                    model.expanded = false;
                    model.value = index+1;
                    if (e_type==='tap') {
                        element.setData('_suppressClose', true);
                        laterSilent(function() {
                            element.removeData('_suppressClose');
                        }, SUPPRESS_DELAY);
                        ulNode = element.getElement('ul[fm-manage]');
                        ulNode && ulNode.focus(true);
                    }
                    // prevent that the focus will be reset to the focusmanager
                    // when re-synced --> we want the focus on the button:
                    ulNode.removeClass('focussed');
                    asyncSilent(function() {
                        element.focus(true);
                    });
                }
            }
        }, 'i-select ul[fm-manage] > li');

        Event.defineEvent(itagName+':valuechange')
             .unPreventable()
             .noRender();

        Event.after(itagName+':change', function(e) {
            var element = e.target,
                prevValue = element.getData('i-select-value'),
                model = element.model,
                newValue = model.value,
                markValue;
            if (prevValue!==newValue) {
                markValue = newValue - 1;

                /**
                * Emitted when a the i-select changes its value
                *
                * @event i-select:valuechange
                * @param e {Object} eventobject including:
                * @param e.target {HtmlElement} the i-select element
                * @param e.prevValue {Number} the selected item, starting with 1
                * @param e.newValue {Number} the selected item, starting with 1
                * @param e.buttonText {String} the text that will appear on the button
                * @param e.listText {String} the text as it is in the list
                * @since 0.1
                */
                element.emit('valuechange', {
                    prevValue: prevValue,
                    newValue: newValue,
                    buttonText: model.buttonTexts[markValue] || model.items[markValue],
                    listText: model.items[markValue]
                });
            }
            element.setData('i-select-value', newValue);
        });

        Itag = IFormElement.subClass(itagName, {
            /*
             *
             * @property attrs
             * @type Object
             * @since 0.0.1
            */
            attrs: {
                expanded: 'boolean',
                value: 'string',
                prop: 'string',
                'invalid-value': 'string'
            },

           /**
            * Redefines the childNodes of both the vnode as well as its related dom-node. The new
            * definition replaces any previous nodes. (without touching unmodified nodes).
            *
            * Syncs the new vnode's childNodes with the dom.
            *
            * @method _setChildNodes
            * @param newVChildNodes {Array} array with vnodes which represent the new childNodes
            * @private
            * @chainable
            * @since 0.0.1
            */
            init: function() {
                var element = this,
                    itemNodes = element.getAll('>i-item'),
                    items = [],
                    buttonTexts = [],
                    content;
                itemNodes.forEach(function(node, i) {
                    var header = node.getElement('i-head');
                    if (header) {
                        buttonTexts[i] = header.getHTML();
                        header.remove(true);
                    }
                    items[items.length] = node.getHTML();
                });

                element.defineWhenUndefined('items', items)
                       .defineWhenUndefined('buttonTexts', buttonTexts);

                // store its current value, so that valueChange-event can fire:
                element.setData('i-select-value', element.model.value);

                // building the template of the itag:
                content = '<button>

</button>';
                // first: outerdiv which will be relative positioned
                // next: innerdiv which will be absolute positioned
                // also: hide the container by default --> updateUI could make it shown
                content += '
' +
                             '
'+
                               '

';
                             '
'+
                           '
';

                element.setupEvents();
                // set the content:
                element.setHTML(content);
            },

            setupEvents: function() {
                var element = this;
                Event.after('tapoutside', function(e) {
                    // at the end of the eventstack: give `tapoutside` a way to set the '_suppressClose'-data when needed
                    // just async will do
                    asyncSilent(function() {
                        if (!element.hasData('_suppressClose') && !element.contains(e.sourceTarget)) {
                            element.model.expanded = false;
                            DOCUMENT.refreshItags();
                        }
                    });
                }, 'i-select');
                element.selfAfter('blurnode', function() {
                    // at the end of the eventstack: give `blurnode` a way to set the '_suppressClose'-data when needed
                    // need a bit more time because there is time inbetween the blur vs click events
                    laterSilent(function() {
                        if (!element.hasData('_suppressClose')) {
                            element.model.expanded = false;
                            DOCUMENT.refreshItags();
                        }
                    }, DELAY_BLURCLOSE);
                });
            },

           /**
            * Redefines the childNodes of both the vnode as well as its related dom-node. The new
            * definition replaces any previous nodes. (without touching unmodified nodes).
            *
            * Syncs the new vnode's childNodes with the dom.
            *
            * @method _setChildNodes
            * @param newVChildNodes {Array} array with vnodes which represent the new childNodes
            * @private
            * @chainable
            * @since 0.0.1
            */
            sync: function() {
console.warn('syncing i-select');
                // inside sync, YOU CANNOT change attributes which are part of `attrs` !!!
                // those actions will be ignored.

                // BE CAREFUL to start async actions here:
                // be aware that before ending, this method can run again
                // if you do, then make sure to handle possible running
                // async actions well !!!
                var element = this,
                    model = element.model,
                    items = model.items,
                    buttonTexts = model.buttonTexts,
                    value = model.value,
                    item, content, buttonText, len, i, markValue,
                    button, container, itemsContainer, hiddenTimer;

                len = items.length;
                (value>len) && (value=0);
                markValue = value - 1;

                if (value>0) {
                    buttonText = buttonTexts[markValue] || items[markValue];
                }
                else {
                    buttonText = model['invalid-value'] || DEFAULT_INVALID_VALUE;
                }

                // rebuild the button:
                button = element.getElement('button');
                button.getElement('div.btntext').setHTML(buttonText);

                container = element.getElement('>div');

                if (model.expanded) {
                    hiddenTimer = container.getData('_hiddenTimer');
                    hiddenTimer && hiddenTimer.cancel();
                    container.setClass(SHOW);
                    container.removeClass(HIDDEN);
                }
                else {
                    container.removeClass(SHOW);
                    // hide the layer completely: we need to access anything underneath:
                    hiddenTimer = laterSilent(function() {
                        container.setClass(HIDDEN);
                    }, 110);
                    container.setData('_hiddenTimer', hiddenTimer);
                }

                itemsContainer = element.getElement('ul[fm-manage]');
                content = '';
                for (i=0; i<len; i++) {
                    item = items[i];
                    content += '
'+item+'
';
                }

                // set the items:
                itemsContainer.setHTML(content, true);
            }
        });

        itagCore.setDirectEventResponse(Itag, ['keypress', 'keydown']);

        window.ITAGS[itagName] = Itag;
    }

    return window.ITAGS[itagName];
};<|endoftext|>


<commit_before>/* @flow */
/* eslint-env mocha */

const should = require('should')

const configHelpers = require('../support/helpers/config')
const cozyHelpers = require('../support/helpers/cozy')
const pouchHelpers = require('../support/helpers/pouch')
const TestHelpers = require('../support/helpers')

describe('Case or encoding change', () => {
  // This test passes on a macOS workstation when using the docker container,
  // since files are stored on an ext4 FS. But Travis macOS workers lack
  // virtualization support, which means the cozy-stack will run directly on
  // macOS with HFS+ and the test will fail (see below).
  // Maybe we could create an ext4 volume on Travis and put the storage files
  // there to better match the production environment?
  if (process.env.TRAVIS && process.platform === 'darwin') {
    it.skip('is unstable on Travis (macOS)')
    return
  }

  let cozy, helpers

  before(configHelpers.createConfig)
  before(configHelpers.registerClient)
  beforeEach(pouchHelpers.createDatabase)
  beforeEach(cozyHelpers.deleteAll)

  afterEach(() => helpers.local.clean())
  afterEach(pouchHelpers.cleanDatabase)
  after(configHelpers.cleanConfig)

  beforeEach(async function() {
    cozy = cozyHelpers.cozy
    helpers = TestHelpers.init(this)

    await helpers.local.setupTrash()
    await helpers.remote.ignorePreviousChanges()
  })

  describe('directory', () => {
    let dir, dir2

    beforeEach(async () => {
      // This will fail with a 409 conflict error when cozy-stack runs directly
      // on macOS & HFS+ because a file with an equivalent name already exists.
      dir = await cozy.files.createDirectory({ name: 'e\u0301' }) // 'é'
      dir2 = await cozy.files.createDirectory({ name: 'foo' })
      await helpers.remote.pullChanges()
      await helpers.syncAll()
      helpers.spyPouch()
      should(await helpers.local.tree()).deepEqual([
        'e\u0301/', // 'é/'
        'foo/'
      ])
    })

    it('remote', async () => {
      await cozy.files.updateAttributesById(dir._id, { name: '\u00e9' }) // 'é'
      await cozy.files.updateAttributesById(dir2._id, { name: 'FOO' })
      await helpers.remote.pullChanges()

      await helpers.syncAll()
      await helpers._local.watcher.start()
      await helpers._local.watcher.stop()
      await helpers.syncAll()

      const tree = await helpers.local.tree()
      switch (process.platform) {
        case 'win32':
          should(tree).deepEqual([
            'FOO/',
            '\u00e9/' // 'é/'
          ])
          break

        case 'darwin':
          should(tree).deepEqual([
            'FOO/',
            '\u00e9/' // 'é/'
          ])
          break

        case 'linux':
          should(tree).deepEqual([
            'FOO/',
            '\u00e9/' // 'é/'
          ])
      }
    })
  })
})
<commit_msg>test: fix macOS normalization test (#1916)<commit_after>/* @flow */
/* eslint-env mocha */

const should = require('should')

const configHelpers = require('../support/helpers/config')
const cozyHelpers = require('../support/helpers/cozy')
const pouchHelpers = require('../support/helpers/pouch')
const TestHelpers = require('../support/helpers')

describe('Case or encoding change', () => {
  // This test passes on a macOS workstation when using the docker container,
  // since files are stored on an ext4 FS. But Travis macOS workers lack
  // virtualization support, which means the cozy-stack will run directly on
  // macOS with HFS+ and the test will fail (see below).
  // Maybe we could create an ext4 volume on Travis and put the storage files
  // there to better match the production environment?
  if (process.env.TRAVIS && process.platform === 'darwin') {
    it.skip('is unstable on Travis (macOS)')
    return
  }

  let cozy, helpers

  before(configHelpers.createConfig)
  before(configHelpers.registerClient)
  beforeEach(pouchHelpers.createDatabase)
  beforeEach(cozyHelpers.deleteAll)

  afterEach(() => helpers.local.clean())
  afterEach(pouchHelpers.cleanDatabase)
  after(configHelpers.cleanConfig)

  beforeEach(async function() {
    cozy = cozyHelpers.cozy
    helpers = TestHelpers.init(this)

    await helpers.local.setupTrash()
    await helpers.remote.ignorePreviousChanges()
  })

  describe('directory', () => {
    let dir, dir2

    beforeEach(async () => {
      // This will fail with a 409 conflict error when cozy-stack runs directly
      // on macOS & HFS+ because a file with an equivalent name already exists.
      dir = await cozy.files.createDirectory({ name: 'e\u0301' }) // 'é'
      dir2 = await cozy.files.createDirectory({ name: 'foo' })
      await helpers.remote.pullChanges()
      await helpers.syncAll()
      helpers.spyPouch()
      should(await helpers.local.tree()).deepEqual([
        'e\u0301/', // 'é/'
        'foo/'
      ])
    })

    it('remote', async () => {
      await cozy.files.updateAttributesById(dir._id, { name: '\u00e9' }) // 'é'
      await cozy.files.updateAttributesById(dir2._id, { name: 'FOO' })
      await helpers.remote.pullChanges()

      await helpers.syncAll()
      await helpers._local.watcher.start()
      await helpers._local.watcher.stop()
      await helpers.syncAll()

      const tree = await helpers.local.tree()
      switch (process.platform) {
        case 'win32':
          should(tree).deepEqual([
            'FOO/',
            '\u00e9/' // 'é/'
          ])
          break

        case 'darwin':
          should(tree).deepEqual([
            'FOO/',
            'e\u0301/' // 'é/'
          ])
          break

        case 'linux':
          should(tree).deepEqual([
            'FOO/',
            '\u00e9/' // 'é/'
          ])
      }
    })
  })
})
<|endoftext|>