<commit_before>import cockpit from "cockpit";
import QUnit from "qunit-tests";

QUnit.test("format", function (assert) {
    assert.equal(cockpit.format("My $adj message with ${amount} of things", { adj: "special", amount: "lots" }),
                 "My special message with lots of things", "named keys");
    assert.equal(cockpit.format("My $0 message with $1 of things", ["special", "lots"]),
                 "My special message with lots of things", "number keys");
    assert.equal(cockpit.format("My $0 message with $1 of things", "special", "lots"),
                 "My special message with lots of things", "vararg keys");
    assert.equal(cockpit.format("My $0 message with lots of things", "special"),
                 "My special message with lots of things", "vararg one key");
    assert.equal(cockpit.format("Undefined $value", { }), "Undefined ", "missing value");

    /* All falsy values except `0` should return the empty string */
    assert.equal(cockpit.format("$0", 0), "0", "`0` as argument");
    assert.equal(cockpit.format("$0", 0.0), "0", "`0.0` as argument");
    assert.equal(cockpit.format("$0", false), "", "`false` as argument");
    assert.equal(cockpit.format("$0", null), "", "`null` as argument");
});

QUnit.test("format_number", function (assert) {
    const checks = [
        [23.4, "23.4", "23,4"],
        [23.46, "23.5", "23,5"],
        [23.44, "23.4", "23,4"],

        [-23.4, "-23.4", "-23,4"],
        [-23.46, "-23.5", "-23,5"],
        [-23.44, "-23.4", "-23,4"],

        [0, "0", "0"],
        [0.001, "0.001", "0,001"],
        [-0.001, "-0.001", "-0,001"],

        [123.0, "123", "123"],
        [123.01, "123", "123"],
        [-123.0, "-123", "-123"],
        [-123.01, "-123", "-123"],
        [null, "", ""],
        [undefined, "", ""],
    ];

    const saved_language = cockpit.language;

    assert.expect(checks.length * 3);

    cockpit.language = 'en';
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_number(checks[i][0]), checks[i][1],
                           "format_number@en(" + checks[i][0] + ") = " + checks[i][1]);
    }

    cockpit.language = 'de';
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_number(checks[i][0]), checks[i][2],
                           "format_number@de(" + checks[i][0] + ") = " + checks[i][2]);
    }

    cockpit.language = 'pt_BR';
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_number(checks[i][0]), checks[i][2],
                           "format_number@pt_BR(" + checks[i][0] + ") = " + checks[i][2]);
    }

    /* restore this as not to break the other tests */
    cockpit.language = saved_language;
});

QUnit.test("format_bytes", function (assert) {
    const checks = [
        [999, 1000, "999"],
        [1934, undefined, "1.89 KiB"],
        [1934, 1000, "1.93 KB"],
        [2000, 1024, "1.95 KiB"],
        [1999, 1000, "2.00 KB"],
        [1999, 1024, "1.95 KiB"],
        [1000000, 1000, "1 MB"],
        [1000001, 1000, "1.00 MB"],
        [1000000, 1024, "977 KiB"],
        [2000000, 1024, "1.91 MiB"],
        [2000000, 1000, "2 MB"],
        [2000001, 1000, "2.00 MB"],
        [2000000, "MB", "2 MB"],
        [2000000, "MiB", "1.91 MiB"],
        [2000000, "KB", "2000 KB"],
        [2000000, "KiB", "1953 KiB"],
        [1, "KB", "0.001 KB"],
        [0, "KB", "0 KB"],
        [undefined, "KB", ""],
        [null, "KB", ""],
    ];

    assert.expect(checks.length * 2);
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_bytes(checks[i][0], checks[i][1]), checks[i][2],
                           "format_bytes(" + checks[i][0] + ", " + String(checks[i][1]) + ") = " + checks[i][2]);
    }
    for (let i = 0; i < checks.length; i++) {
        const split = checks[i][2].split(" ");
        assert.deepEqual(cockpit.format_bytes(checks[i][0], checks[i][1], true), split,
                         "format_bytes(" + checks[i][0] + ", " + String(checks[i][1]) + ", true) = " + split);
    }
});

QUnit.test("get_byte_units", function (assert) {
    const mib = 1024 * 1024;
    const gib = mib * 1024;
    const tib = gib * 1024;

    const mib_unit = { factor: mib, name: "MiB" };
    const gib_unit = { factor: gib, name: "GiB" };
    const tib_unit = { factor: tib, name: "TiB" };

    function selected(unit) {
        return { factor: unit.factor, name: unit.name, selected: true };
    }

    const checks = [
        [0 * mib, 1024, [selected(mib_unit), gib_unit, tib_unit]],
        [20 * mib, 1024, [selected(mib_unit), gib_unit, tib_unit]],
        [200 * mib, 1024, [selected(mib_unit), gib_unit, tib_unit]],
        [2000 * mib, 1024, [selected(mib_unit), gib_unit, tib_unit]],
        [20000 * mib, 1024, [mib_unit, selected(gib_unit), tib_unit]],
        [20 * gib, 1024, [mib_unit, selected(gib_unit), tib_unit]],
        [200 * gib, 1024, [mib_unit, selected(gib_unit), tib_unit]],
        [2000 * gib, 1024, [mib_unit, selected(gib_unit), tib_unit]],
        [20000 * gib, 1024, [mib_unit, gib_unit, selected(tib_unit)]]
    ];

    assert.expect(checks.length);
    for (let i = 0; i < checks.length; i++) {
        assert.deepEqual(cockpit.get_byte_units(checks[i][0], checks[i][1]), checks[i][2],
                         "get_byte_units(" + checks[i][0] + ", " + checks[i][1] + ") = " + JSON.stringify(checks[i][2]));
    }
});

QUnit.test("format_bytes_per_sec", function (assert) {
    const checks = [
        [2555, "2.50 KiB/s"]
    ];

    assert.expect(checks.length);
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_bytes_per_sec(checks[i][0]), checks[i][1],
                           "format_bytes_per_sec(" + checks[i][0] + ") = " + checks[i][1]);
    }
});

QUnit.test("format_bits_per_sec", function (assert) {
    const checks = [
        [55, "55 bps"],
        [55.23456789, "55.2 bps"],
        [55.98765432, "56.0 bps"],
        [2555, "2.56 Kbps"],
        [2000, "2 Kbps"],
        [2003, "2.00 Kbps"]
    ];

    assert.expect(checks.length);
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_bits_per_sec(checks[i][0]), checks[i][1],
                           "format_bits_per_sec(" + checks[i][0] + ") = " + checks[i][1]);
    }
});

QUnit.start();
<commit_msg>base1: Add unit test for format_number() for very small values<commit_after>import cockpit from "cockpit";
import QUnit from "qunit-tests";

QUnit.test("format", function (assert) {
    assert.equal(cockpit.format("My $adj message with ${amount} of things", { adj: "special", amount: "lots" }),
                 "My special message with lots of things", "named keys");
    assert.equal(cockpit.format("My $0 message with $1 of things", ["special", "lots"]),
                 "My special message with lots of things", "number keys");
    assert.equal(cockpit.format("My $0 message with $1 of things", "special", "lots"),
                 "My special message with lots of things", "vararg keys");
    assert.equal(cockpit.format("My $0 message with lots of things", "special"),
                 "My special message with lots of things", "vararg one key");
    assert.equal(cockpit.format("Undefined $value", { }), "Undefined ", "missing value");

    /* All falsy values except `0` should return the empty string */
    assert.equal(cockpit.format("$0", 0), "0", "`0` as argument");
    assert.equal(cockpit.format("$0", 0.0), "0", "`0.0` as argument");
    assert.equal(cockpit.format("$0", false), "", "`false` as argument");
    assert.equal(cockpit.format("$0", null), "", "`null` as argument");
});

QUnit.test("format_number", function (assert) {
    const checks = [
        [23.4, "23.4", "23,4"],
        [23.46, "23.5", "23,5"],
        [23.44, "23.4", "23,4"],

        [-23.4, "-23.4", "-23,4"],
        [-23.46, "-23.5", "-23,5"],
        [-23.44, "-23.4", "-23,4"],

        [0, "0", "0"],
        [0.001, "0.001", "0,001"],
        [-0.001, "-0.001", "-0,001"],
        // smaller values get rounded up
        [0.0003, "0.001", "0,001"],
        [-0.0003, "-0.001", "-0,001"],

        [123.0, "123", "123"],
        [123.01, "123", "123"],
        [-123.0, "-123", "-123"],
        [-123.01, "-123", "-123"],
        [null, "", ""],
        [undefined, "", ""],
    ];

    const saved_language = cockpit.language;

    assert.expect(checks.length * 3);

    cockpit.language = 'en';
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_number(checks[i][0]), checks[i][1],
                           "format_number@en(" + checks[i][0] + ") = " + checks[i][1]);
    }

    cockpit.language = 'de';
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_number(checks[i][0]), checks[i][2],
                           "format_number@de(" + checks[i][0] + ") = " + checks[i][2]);
    }

    cockpit.language = 'pt_BR';
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_number(checks[i][0]), checks[i][2],
                           "format_number@pt_BR(" + checks[i][0] + ") = " + checks[i][2]);
    }

    /* restore this as not to break the other tests */
    cockpit.language = saved_language;
});

QUnit.test("format_bytes", function (assert) {
    const checks = [
        [999, 1000, "999"],
        [1934, undefined, "1.89 KiB"],
        [1934, 1000, "1.93 KB"],
        [2000, 1024, "1.95 KiB"],
        [1999, 1000, "2.00 KB"],
        [1999, 1024, "1.95 KiB"],
        [1000000, 1000, "1 MB"],
        [1000001, 1000, "1.00 MB"],
        [1000000, 1024, "977 KiB"],
        [2000000, 1024, "1.91 MiB"],
        [2000000, 1000, "2 MB"],
        [2000001, 1000, "2.00 MB"],
        [2000000, "MB", "2 MB"],
        [2000000, "MiB", "1.91 MiB"],
        [2000000, "KB", "2000 KB"],
        [2000000, "KiB", "1953 KiB"],
        [1, "KB", "0.001 KB"],
        [0, "KB", "0 KB"],
        [undefined, "KB", ""],
        [null, "KB", ""],
    ];

    assert.expect(checks.length * 2);
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_bytes(checks[i][0], checks[i][1]), checks[i][2],
                           "format_bytes(" + checks[i][0] + ", " + String(checks[i][1]) + ") = " + checks[i][2]);
    }
    for (let i = 0; i < checks.length; i++) {
        const split = checks[i][2].split(" ");
        assert.deepEqual(cockpit.format_bytes(checks[i][0], checks[i][1], true), split,
                         "format_bytes(" + checks[i][0] + ", " + String(checks[i][1]) + ", true) = " + split);
    }
});

QUnit.test("get_byte_units", function (assert) {
    const mib = 1024 * 1024;
    const gib = mib * 1024;
    const tib = gib * 1024;

    const mib_unit = { factor: mib, name: "MiB" };
    const gib_unit = { factor: gib, name: "GiB" };
    const tib_unit = { factor: tib, name: "TiB" };

    function selected(unit) {
        return { factor: unit.factor, name: unit.name, selected: true };
    }

    const checks = [
        [0 * mib, 1024, [selected(mib_unit), gib_unit, tib_unit]],
        [20 * mib, 1024, [selected(mib_unit), gib_unit, tib_unit]],
        [200 * mib, 1024, [selected(mib_unit), gib_unit, tib_unit]],
        [2000 * mib, 1024, [selected(mib_unit), gib_unit, tib_unit]],
        [20000 * mib, 1024, [mib_unit, selected(gib_unit), tib_unit]],
        [20 * gib, 1024, [mib_unit, selected(gib_unit), tib_unit]],
        [200 * gib, 1024, [mib_unit, selected(gib_unit), tib_unit]],
        [2000 * gib, 1024, [mib_unit, selected(gib_unit), tib_unit]],
        [20000 * gib, 1024, [mib_unit, gib_unit, selected(tib_unit)]]
    ];

    assert.expect(checks.length);
    for (let i = 0; i < checks.length; i++) {
        assert.deepEqual(cockpit.get_byte_units(checks[i][0], checks[i][1]), checks[i][2],
                         "get_byte_units(" + checks[i][0] + ", " + checks[i][1] + ") = " + JSON.stringify(checks[i][2]));
    }
});

QUnit.test("format_bytes_per_sec", function (assert) {
    const checks = [
        [2555, "2.50 KiB/s"]
    ];

    assert.expect(checks.length);
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_bytes_per_sec(checks[i][0]), checks[i][1],
                           "format_bytes_per_sec(" + checks[i][0] + ") = " + checks[i][1]);
    }
});

QUnit.test("format_bits_per_sec", function (assert) {
    const checks = [
        [55, "55 bps"],
        [55.23456789, "55.2 bps"],
        [55.98765432, "56.0 bps"],
        [2555, "2.56 Kbps"],
        [2000, "2 Kbps"],
        [2003, "2.00 Kbps"]
    ];

    assert.expect(checks.length);
    for (let i = 0; i < checks.length; i++) {
        assert.strictEqual(cockpit.format_bits_per_sec(checks[i][0]), checks[i][1],
                           "format_bits_per_sec(" + checks[i][0] + ") = " + checks[i][1]);
    }
});

QUnit.start();
<|endoftext|>


<commit_before>var superagent = require('superagent-promise')(require('superagent'), Promise);
var _ = require('underscore');

class MeetingMapper {

    //============================================================
    //
    //
    //============================================================
    async query () {

    	var url = 'https://chm.cbd.int/api/v2013/index/select?fl=id,title_t,title_AR_t,title_ES_t,title_FR_t,title_RU_t,title_ZH_t,themes_ss,symbol_s,eventCountry_s,startDate_dt,updatedDate_dt,url_ss&q=NOT+version_s:*+AND+realm_ss:chm+AND+schema_s:meeting&rows=9999&start=0&wt=json';

    	var res = await superagent.get(url).end();

		return res.body.response.docs.map(document => new Meeting({

            protocolVersion:        1,
            id:                     document.id,
            treaty:                 _.contains(document.themes_ss, 'CBD-SUBJECT-ABS')?'nagoya': _.contains(document.themes_ss, 'CBD-SUBJECT-CPB')?'cartagena':'cbd',
            url:                    document.url_ss.length ? document.url_ss[0] : null,
            title:                  [ { language: 'en', value: document.title_t },{ language: 'ar', value: document.title_AR_t},{ language: 'es', value: document.title_ES_t},{ language: 'fr', value: document.title_FR_t},{ language: 'ru', value: document.title_RU_t},{ language: 'zh', value: document.title_ZH_t} ],
        //  description:            { type: Array, elementType: LocalizableString,  required: false },   // Short description of the meeting. This is a list of Localizable String complex type objects.
            start:                  document.startDate_dt,
        //  end:                    { type: Date,                                   required: false },   // End date for the meeting
        //  repetition:             { type: String,                                 required: false },   // Frequency of this meeting. Use one of the predefined values from MeetingRepetition enumeration
        //  kind:                   { type: String,                                 required: false },   // Kind of meeting (official, partner, interest etc.). Use one of the predefined values from MeetingKind enumeration
            type:                   document.symbol_s.match(/^COP-\d+$/)?'COP': (document.symbol_s==='EXCOP-01')?'COP': '', //{ type: String,                                 required: false },   // Type of meeting. Use one of the predefined values from MeetingType enumeration
        //  access:                 { type: String,                                 required: false },   // Specifies if there is public access to the event. Use one of the predefined values from MeetingAccess enumeration
        //  status:                 { type: String,                                 required: false },   // Status of the event. Use one of the predefined values from MeetingStatus enumeration
        //  imageUrl:               { type: String,                                 required: false },   // URL of a small, representative image for the meeting, displayed by InforMEA to display a listing of the meetings.
        //  imageCopyright:         { type: String,                                 required: false },   // Copyright statement of the above image
        //  location:               { type: String,                                 required: false },   // Location of the event (ex. Nottingham Castle, grand ball room)
        //   city:                   document.eventCity_t,
            country:                (document.eventCountry_s||'').toUpperCase(),
        //  latitude:               { type: Number,                                 required: false },   // Latitude of the event place in decimal degrees (ex. 23.456323). This value may be used to represent the event on a map using existing geographical applications such as Google Maps
        //  longitude:              { type: Number,                                 required: false },   // Longitude of the event place in decimal degrees (ex. 23.456323). This value may be used to represent the event on a map using existing geographical applications such as Google Maps
        //    updated:                document.updatedDate_dt

        }));
    }
}

export default new MeetingMapper();
<commit_msg>Display other languages text only if not equals to english version<commit_after>var superagent = require('superagent-promise')(require('superagent'), Promise);
var _ = require('underscore');

class MeetingMapper {

    //============================================================
    //
    //
    //============================================================
    async query () {

    	var url = 'https://chm.cbd.int/api/v2013/index/select?fl=id,title_t,title_AR_t,title_ES_t,title_FR_t,title_RU_t,title_ZH_t,themes_ss,symbol_s,eventCountry_s,startDate_dt,updatedDate_dt,url_ss&q=NOT+version_s:*+AND+realm_ss:chm+AND+schema_s:meeting&rows=9999&start=0&wt=json';

    	var res = await superagent.get(url).end();

		return res.body.response.docs.map(document => new Meeting({

            protocolVersion:        1,
            id:                     document.id,
            treaty:                 _.contains(document.themes_ss, 'CBD-SUBJECT-ABS')?'nagoya': _.contains(document.themes_ss, 'CBD-SUBJECT-CPB')?'cartagena':'cbd',
            url:                    document.url_ss.length ? document.url_ss[0] : null,
            title:                  toLocalizableString(document, 'title'),
        //  description:            { type: Array, elementType: LocalizableString,  required: false },   // Short description of the meeting. This is a list of Localizable String complex type objects.
            start:                  document.startDate_dt,
        //  end:                    { type: Date,                                   required: false },   // End date for the meeting
        //  repetition:             { type: String,                                 required: false },   // Frequency of this meeting. Use one of the predefined values from MeetingRepetition enumeration
        //  kind:                   { type: String,                                 required: false },   // Kind of meeting (official, partner, interest etc.). Use one of the predefined values from MeetingKind enumeration
            type:                   document.symbol_s.match(/^COP-\d+$/)?'COP': (document.symbol_s==='EXCOP-01')?'COP': '', //{ type: String,                                 required: false },   // Type of meeting. Use one of the predefined values from MeetingType enumeration
        //  access:                 { type: String,                                 required: false },   // Specifies if there is public access to the event. Use one of the predefined values from MeetingAccess enumeration
        //  status:                 { type: String,                                 required: false },   // Status of the event. Use one of the predefined values from MeetingStatus enumeration
        //  imageUrl:               { type: String,                                 required: false },   // URL of a small, representative image for the meeting, displayed by InforMEA to display a listing of the meetings.
        //  imageCopyright:         { type: String,                                 required: false },   // Copyright statement of the above image
        //  location:               { type: String,                                 required: false },   // Location of the event (ex. Nottingham Castle, grand ball room)
        //   city:                   document.eventCity_t,
            country:                (document.eventCountry_s||'').toUpperCase(),
        //  latitude:               { type: Number,                                 required: false },   // Latitude of the event place in decimal degrees (ex. 23.456323). This value may be used to represent the event on a map using existing geographical applications such as Google Maps
        //  longitude:              { type: Number,                                 required: false },   // Longitude of the event place in decimal degrees (ex. 23.456323). This value may be used to represent the event on a map using existing geographical applications such as Google Maps
        //    updated:                document.updatedDate_dt

        }));
    }
}

function toLocalizableString(document, field) {

    var texts = [{ language: 'en', value: document[field+'_t'] }];

    if(document[field+'_AR_t'] && document[field+'_AR_t']!=document[field+'_t']) texts.push({ language: 'ar', value: document[field+'_AR_t'] });
    if(document[field+'_ES_t'] && document[field+'_ES_t']!=document[field+'_t']) texts.push({ language: 'es', value: document[field+'_ES_t'] });
    if(document[field+'_FR_t'] && document[field+'_FR_t']!=document[field+'_t']) texts.push({ language: 'fr', value: document[field+'_FR_t'] });
    if(document[field+'_RU_t'] && document[field+'_RU_t']!=document[field+'_t']) texts.push({ language: 'ru', value: document[field+'_RU_t'] });
    if(document[field+'_ZH_t'] && document[field+'_ZH_t']!=document[field+'_t']) texts.push({ language: 'zh', value: document[field+'_ZH_t'] });

    return texts;
}

export default new MeetingMapper();
<|endoftext|>


<commit_before>"use strict";
/*jslint node: true, vars: true, plusplus: true, forin: true */
var next = setImmediate || process.nextTick;

var async = require('async');
var db = require('./db');
var pseudo = require('./pseudo-request');
var querystring = require('querystring');

/*
   (All the file names in this description and code are Kilroy object idtags.)

   This garbage collector cleans all and only the needed files.

   The actual "file" access is done through the db module, which must supply methods
   initialize, mark, and sweep. The mark and sweep could be implemented (and initially
   was implemented) by copying the needed files from oldspace to newspace during "mark",
   and then deleting oldspace and renaming newspace to old during "sweep".

   The GC maintains a list of unprocessed oldspace file name references.
   Each file is a JSON serialization of an object that may reference other objects.
   Processing consists of reading an object from oldspace, marking it,
   and adding any child references to the the list.

   Args:
   * A function(name) that will take care of any mutable object references (places) that
     the gc finds. For example, it might copy from an old mutables space to a new.
   * A function(name, callback(err)) that will take care of any media references that
     the gc finds. Media are (possibly large) immutable data that do not reference anything themselves.
   * A callback() that the gc can use to add names to the list. When the GC's
     own list is empty and this callback answers falsy, the GC is completed.
	 The callback can answer an empty list, meaning that the sender is still asynchronously
	 retrieving more data, and the GC should ask again later.
   * A callback(error, resultInformationObject) to be run when the gc is completed.

   This is a realtime garbage collector in the sense that it can run concurrently with
   evolution of the data. It does this through the following simplifying requirements:
   * All oldspace files given to the gc are immutable. If the gc or the evolving system write
     something to newspace many times, the order doesn't matter because all versions are the same.
   * There are no cycles among the references. (Many parent objects reference can reference
     the same child, but the children cannot reference one of their own ancestors.)
   * The evolving system should write new data to both oldspace and newspace (mutables, immutables,
     and media), and read from oldspace until the gc completes.

   This gc is:
   * efficient in working memory -- it only keeps one object at a time plus its running queue.
   * not disruptive -- it runs only when nothing else is happening, and then only for long enough
     to process just one name (which are always quite short). Even the read and most writes are asynchronous.
   * robust -- it can fail or be shut down in the middle without damage. (oldspace is still valid)
   * simple -- the addRoots callback can answer duplicate names in the same or subsequent calls, and
     the system can keep other (e.g., mutable) objects in the oldspace directory (as long as it
     maintains and cleans them on its own).
   * inefficient -- which is ok because of the above and being realtime, as long as it does
     eventually complete. To make progress, it is only necessary that names are added more
	 slowly than they are processed.
*/
function collectImmutables(copyMutable, copyMedia, addRoots, progress, callback) {
	var unprocessedNames = [];
	var tick = function () {
		if (!unprocessedNames.length) { unprocessedNames = addRoots(); }
		if (!unprocessedNames) {
			progress.totalTime = new Date().getTime() - progress.startTime;
			return callback(null, progress);
		}
		if (!unprocessedNames.length) { return setTimeout(tick, 20); }
		if (unprocessedNames.length > progress.maxQueue) {
			progress.maxQueue = unprocessedNames.length;  // Just nice to know.
		}
		var name = unprocessedNames.pop(); // Much faster than shift. We don't care about order.
		// The goal here is to keep the server responsive by only taking a tiny, well-bounded
		// time slice here, without growing memory state any more than we already have to.
		// Thus, we asynchronously processes one name completely before starting the next.
		db.mark(name, function (err, obj) {
			if (err) { pseudo.info({statusCode: 500, url: '/gcRef?error=' + err.name + '&desc=' + err.message}); }
			if (!obj) { return next(tick); }
			progress.things++;
			copyMedia(obj.materials || [], function (copyErr) {
				if (copyErr) { return callback(copyErr, progress); }
				async.eachSeries(obj.children || [], function (child, cb) {
					unprocessedNames.push(child.idvtag || child.idtag);
					// The presence of a version tag indicates that idtag is a mutable wrapper.
					// We don't dig inside (caller must), but we do apply copier and recurse through immutable idvtag part.
					if (child.idvtag) {
						copyMutable(child.idtag, cb);
					} else {
						cb();
					}
				}, function (err) {
					if (err) { return callback(err, progress); }
					next(tick);
				});
			});
		});
	};
	next(tick); // Don't even start until there's nothing else to do.
}

// Go through all people, copy their scenes, and collectMutables of all the scene versions.
function collect(callback) {
	var progress = {people: 0, scenes: 0, versions: 0, places: 0, things: 0, media: 0, startTime: new Date().getTime(), maxQueue: 0}; // getTime so easily printed as url querystring
	// collectImmutables will periodically ask for these and act on them (coming back for more if empty).
	var immutables = [];  // It won't stop until this is empty...
	var examined = false;  // ... and  we have finished examining everything.
	var addRoots = function () {  // Callback collectImmutables uses to get the above.
		var these = immutables;
		immutables = [];
		return (these.length || !examined) ? these : null;
	};

	var doError = function (err) {
		if (err) { immutables = []; examined = true; console.log('FIXME', err); callback(err); }
		return err;
	};
	var copyMutable = function (idtag, cb) {
		db.mark(idtag, function (err, newlyMarked) {
			if (newlyMarked) { progress.places++; }
			cb(err);
		}, 'forceProcessingEvenIfUpdated');
	};
	var copyMaterials = function (materials, cb) {
		db.markMaterials(materials, function (err, nNewlyMarked) {
			progress.media += nNewlyMarked;
			cb(err);
		});
	};
	var started = false;  // We don't collectImmutables until our first iteration.
	db.iterateUsers(function (user, userCb) {
		progress.people++;
		async.eachSeries(user.scenes || [], function (sceneIdtag, sceneCb) { // each scene of the user...
			progress.scenes++;
			db.mark(sceneIdtag, function (err, scene) {
				if (!doError(err) && scene) { // scene is null if it was already marked
					var key;
					// Add each mentioned version to our immutables list...
					//
					// Each scene version specifies a complete immutable tree, including
					// any necessary versions of child mutables. So if we handle all
					// toplevel (scene) versions, then we do not have to descend into
					// all the versions of child mutables.
					//
					// We could trim the versions here to remove expired ones.
					// (If we do in the future, we should check with chat and
					// skip any that have active sessions, because it's not nice to remove
					// something that someone might use the back button to reach.)
					// It is not necessary to trim child mutables, as the expired version objects
					// will end up being trimmed when their parents are, and the references
					// within the list are not long and will be trimmed when the object is next
					// saved.
					var versions = scene.versions || {oldStyle: scene.idvtag};
					for (key in versions) { progress.versions++; immutables.push(versions[key]); }
					if (!started) {
						started = true;
						collectImmutables(copyMutable, copyMaterials, addRoots, progress, callback);
					}
				}
				sceneCb(err);
			}, 'forceProcessingEvenIfUpdated'); // there won't be duplicates, so we don't have to worry about hitting them more than once.
		}, userCb);
	}, function (err) { if (!doError(err)) { examined = true; } });
}

// Wait a short while after the first activity in db.
// Then GC from db to db2.
// When that's done, log the results, reverse and cleanup, and repeat.
exports.pingPong = function (base, delay) {
	var requested = false;
	var inProgress = false;
	var ping = function () {
		if (inProgress) { return exports.requestGC(); }
		if (requested) { clearTimeout(requested); }
		requested = null;
		inProgress = true;
		collect(function (error, progress) {
			var done = function (err) {
				progress = progress || {};
				var stats = { // An object recognized by our standard logger.
				    url: '/gc?' + querystring.encode(progress || {}),
					runtime: progress && progress.totalTime
				};
				inProgress = false;
				if (error) {
					stats.url += '&errno=' + error.errno + '&code=' + error.code + '&error=' + error.message;
					stats.statusCode = 500;
					pseudo.info(stats);
				} else {
					pseudo.info(stats);
					if (requested) { next(ping); }
				}
			};
			if (error) {
				done(error);
			} else {
				db.sweep(progress, done);
			}
		});
	};
	// At one time, we used fs.watch to ping when there was a change to oldspace.
	// fs.watch is documented as unstable. On OSX, I find that sometimes if I have an error
	// (e.g., in my code) while watching a directory, watch will not notice a file being
	// added to that directory (e.g., touch <dirname>/x), (even after a reboot, I think).
	// This can be fixed by touch <dirname> (directly, no x), which seems to reset things.
	// Additionally, this doesn't watch oldspace AND oldmutable, and doesn't watch split subdirectories.
	exports.requestGC = function () {
		if (requested) { return; }
		requested = setTimeout(ping, delay);
	};
	// If the gc ran successfully before, newspace/markings will be clean.
	// However, if it failed, there could be crud there that will confuse us, so
	// make sure everything is clean at startup.
	db.initialize(base);
};

//exports.collect('people/', 'db/', 'db2/', console.log);

<commit_msg>Untabify, and integrate timing with info logging.<commit_after>"use strict";
/*jslint node: true, vars: true, plusplus: true, forin: true */
var next = setImmediate || process.nextTick;

var async = require('async');
var db = require('./db');
var pseudo = require('./pseudo-request');
var querystring = require('querystring');

/*
   (All the file names in this description and code are Kilroy object idtags.)

   This garbage collector cleans all and only the needed files.

   The actual "file" access is done through the db module, which must supply methods
   initialize, mark, and sweep. The mark and sweep could be implemented (and initially
   was implemented) by copying the needed files from oldspace to newspace during "mark",
   and then deleting oldspace and renaming newspace to old during "sweep".

   The GC maintains a list of unprocessed oldspace file name references.
   Each file is a JSON serialization of an object that may reference other objects.
   Processing consists of reading an object from oldspace, marking it,
   and adding any child references to the the list.

   Args:
   * A function(name) that will take care of any mutable object references (places) that
     the gc finds. For example, it might copy from an old mutables space to a new.
   * A function(name, callback(err)) that will take care of any media references that
     the gc finds. Media are (possibly large) immutable data that do not reference anything themselves.
   * A callback() that the gc can use to add names to the list. When the GC's
     own list is empty and this callback answers falsy, the GC is completed.
     The callback can answer an empty list, meaning that the sender is still asynchronously
     retrieving more data, and the GC should ask again later.
   * A callback(error, resultInformationObject) to be run when the gc is completed.

   This is a realtime garbage collector in the sense that it can run concurrently with
   evolution of the data. It does this through the following simplifying requirements:
   * All oldspace files given to the gc are immutable. If the gc or the evolving system write
     something to newspace many times, the order doesn't matter because all versions are the same.
   * There are no cycles among the references. (Many parent objects reference can reference
     the same child, but the children cannot reference one of their own ancestors.)
   * The evolving system should write new data to both oldspace and newspace (mutables, immutables,
     and media), and read from oldspace until the gc completes.

   This gc is:
   * efficient in working memory -- it only keeps one object at a time plus its running queue.
   * not disruptive -- it runs only when nothing else is happening, and then only for long enough
     to process just one name (which are always quite short). Even the read and most writes are asynchronous.
   * robust -- it can fail or be shut down in the middle without damage. (oldspace is still valid)
   * simple -- the addRoots callback can answer duplicate names in the same or subsequent calls, and
     the system can keep other (e.g., mutable) objects in the oldspace directory (as long as it
     maintains and cleans them on its own).
   * inefficient -- which is ok because of the above and being realtime, as long as it does
     eventually complete. To make progress, it is only necessary that names are added more
     slowly than they are processed.
*/
function collectImmutables(copyMutable, copyMedia, addRoots, progress, callback) {
    var unprocessedNames = [];
    var tick = function () {
        if (!unprocessedNames.length) { unprocessedNames = addRoots(); }
        if (!unprocessedNames) {
            progress.totalTime = new Date().getTime() - progress.startTime;
            return callback(null, progress);
        }
        if (!unprocessedNames.length) { return setTimeout(tick, 20); }
        if (unprocessedNames.length > progress.maxQueue) {
            progress.maxQueue = unprocessedNames.length;  // Just nice to know.
        }
        var name = unprocessedNames.pop(); // Much faster than shift. We don't care about order.
        // The goal here is to keep the server responsive by only taking a tiny, well-bounded
        // time slice here, without growing memory state any more than we already have to.
        // Thus, we asynchronously processes one name completely before starting the next.
        db.mark(name, function (err, obj) {
            if (err) { pseudo.info({statusCode: 500, url: '/gcRef?error=' + err.name + '&desc=' + err.message}); }
            if (!obj) { return next(tick); }
            progress.things++;
            copyMedia(obj.materials || [], function (copyErr) {
                if (copyErr) { return callback(copyErr, progress); }
                async.eachSeries(obj.children || [], function (child, cb) {
                    unprocessedNames.push(child.idvtag || child.idtag);
                    // The presence of a version tag indicates that idtag is a mutable wrapper.
                    // We don't dig inside (caller must), but we do apply copier and recurse through immutable idvtag part.
                    if (child.idvtag) {
                        copyMutable(child.idtag, cb);
                    } else {
                        cb();
                    }
                }, function (err) {
                    if (err) { return callback(err, progress); }
                    next(tick);
                });
            });
        });
    };
    next(tick); // Don't even start until there's nothing else to do.
}

// Go through all people, copy their scenes, and collectMutables of all the scene versions.
function collect(callback) {
    var progress = {people: 0, scenes: 0, versions: 0, places: 0, things: 0, media: 0, startTime: new Date().getTime(), maxQueue: 0}; // getTime so easily printed as url querystring
    // collectImmutables will periodically ask for these and act on them (coming back for more if empty).
    var immutables = [];  // It won't stop until this is empty...
    var examined = false;  // ... and  we have finished examining everything.
    var addRoots = function () {  // Callback collectImmutables uses to get the above.
        var these = immutables;
        immutables = [];
        return (these.length || !examined) ? these : null;
    };

    var doError = function (err) {
        if (err) { immutables = []; examined = true; console.log('FIXME', err); callback(err); }
        return err;
    };
    var copyMutable = function (idtag, cb) {
        db.mark(idtag, function (err, newlyMarked) {
            if (newlyMarked) { progress.places++; }
            cb(err);
        }, 'forceProcessingEvenIfUpdated');
    };
    var copyMaterials = function (materials, cb) {
        db.markMaterials(materials, function (err, nNewlyMarked) {
            progress.media += nNewlyMarked;
            cb(err);
        });
    };
    var started = false;  // We don't collectImmutables until our first iteration.
    db.iterateUsers(function (user, userCb) {
        progress.people++;
        async.eachSeries(user.scenes || [], function (sceneIdtag, sceneCb) { // each scene of the user...
            progress.scenes++;
            db.mark(sceneIdtag, function (err, scene) {
                if (!doError(err) && scene) { // scene is null if it was already marked
                    var key;
                    // Add each mentioned version to our immutables list...
                    //
                    // Each scene version specifies a complete immutable tree, including
                    // any necessary versions of child mutables. So if we handle all
                    // toplevel (scene) versions, then we do not have to descend into
                    // all the versions of child mutables.
                    //
                    // We could trim the versions here to remove expired ones.
                    // (If we do in the future, we should check with chat and
                    // skip any that have active sessions, because it's not nice to remove
                    // something that someone might use the back button to reach.)
                    // It is not necessary to trim child mutables, as the expired version objects
                    // will end up being trimmed when their parents are, and the references
                    // within the list are not long and will be trimmed when the object is next
                    // saved.
                    var versions = scene.versions || {oldStyle: scene.idvtag};
                    for (key in versions) { progress.versions++; immutables.push(versions[key]); }
                    if (!started) {
                        started = true;
                        collectImmutables(copyMutable, copyMaterials, addRoots, progress, callback);
                    }
                }
                sceneCb(err);
            }, 'forceProcessingEvenIfUpdated'); // there won't be duplicates, so we don't have to worry about hitting them more than once.
        }, userCb);
    }, function (err) { if (!doError(err)) { examined = true; } });
}

// Wait a short while after the first activity in db.
// Then GC from db to db2.
// When that's done, log the results, reverse and cleanup, and repeat.
exports.pingPong = function (base, delay) {
    var requested = false;
    var inProgress = false;
    var ping = function () {
        if (inProgress) { return exports.requestGC(); }
        if (requested) { clearTimeout(requested); }
        requested = null;
        inProgress = true;
        collect(function (error, progress) {
            var done = function (err) {
                progress = progress || {};
                var stats = { // An object recognized by our standard logger.
                    url: '/gc?' + querystring.encode(progress || {}),
                    startTime: progress.startTime,
                    runtime: progress && progress.totalTime
                };
                inProgress = false;
                if (error) {
                    stats.url += '&errno=' + error.errno + '&code=' + error.code + '&error=' + error.message;
                    stats.statusCode = 500;
                    pseudo.info(stats);
                } else {
                    pseudo.info(stats);
                    if (requested) { next(ping); }
                }
            };
            if (error) {
                done(error);
            } else {
                db.sweep(progress, done);
            }
        });
    };
    // At one time, we used fs.watch to ping when there was a change to oldspace.
    // fs.watch is documented as unstable. On OSX, I find that sometimes if I have an error
    // (e.g., in my code) while watching a directory, watch will not notice a file being
    // added to that directory (e.g., touch <dirname>/x), (even after a reboot, I think).
    // This can be fixed by touch <dirname> (directly, no x), which seems to reset things.
    // Additionally, this doesn't watch oldspace AND oldmutable, and doesn't watch split subdirectories.
    exports.requestGC = function () {
        if (requested) { return; }
        requested = setTimeout(ping, delay);
    };
    // If the gc ran successfully before, newspace/markings will be clean.
    // However, if it failed, there could be crud there that will confuse us, so
    // make sure everything is clean at startup.
    db.initialize(base);
};

//exports.collect('people/', 'db/', 'db2/', console.log);
<|endoftext|>


<commit_before>'use strict';

var should = require('should');
var async = require('async');

var Anyfetch = require('../lib/index.js');
var configuration = require('../config/configuration.js');

// TODO: use mock server

describe('<High-level helper functions>', function() {
  var anyfetch;
  var anyfetchBasic = new Anyfetch(configuration.test.login, configuration.test.password);

  // Retrieve token from credentials
  before(function(done) {
    anyfetchBasic.getToken(function(err, res) {
      anyfetch = new Anyfetch(res.body.token);
      done(err);
    });
  });

  describe('getDocumentWithInfo', function() {
    var documentId;

    before(function(done) {
      anyfetch.postDocument(configuration.test.fakeDocument, function(err, res) {
        if(res.body && res.body.id) {
          documentId = res.body.id;
        }
        done(err);
      });
    });

    it('should get document and populate `document_type` and `provider`', function(done) {
      anyfetch.getDocumentWithInfo(documentId, function(err, doc) {
        should(err).not.be.ok;
        should(doc).be.ok;
        doc.should.have.properties('id', 'identifier', 'provider', 'document_type');
        doc.provider.should.have.properties('client', 'name', 'document_count');
        doc.document_type.should.have.properties('id', 'name', 'templates');

        done();
      });
    });

    it('same thing by identifier', function(done) {
      var identifier = configuration.test.fakeDocument.identifier;
      anyfetch.getDocumentByIdentifierWithInfo(identifier, function(err, doc) {
        should(err).not.be.ok;
        should(doc).be.ok;
        doc.should.have.properties('id', 'identifier', 'provider', 'document_type');
        doc.provider.should.have.properties('client', 'name', 'document_count');
        doc.document_type.should.have.properties('id', 'name', 'templates');

        done();
      });
    });

    after(function(done) {
      anyfetch.deleteDocumentById(documentId, done);
    });
  });

  describe('getDocumentsWithInfo', function() {

    // Prepare two documents
    before(function(done) {
      async.parallel([
        function(cb) {
          anyfetch.postDocument(configuration.test.fakeDocument, cb);
        },
        function(cb) {
          anyfetch.postDocument(configuration.test.fakeDocument2, cb);
        }
      ], done);
    });

    it('should get all documents and populate their `document_type` and `provider`', function(done) {
      anyfetch.getDocumentsWithInfo(function(err, docs) {
        docs.data.forEach(function(doc) {
          should(err).not.be.ok;
          should(doc).be.ok;
          doc.should.have.properties('id', 'identifier', 'provider', 'document_type');
          doc.provider.should.have.properties('client', 'name', 'document_count');
          doc.document_type.should.have.properties('id', 'name', 'templates');
        });

        done();
      });
    });

    after(function(done) {
      async.parallel([
        function(cb) {
          var identifier = configuration.test.fakeDocument.identifier;
          anyfetch.deleteDocumentByIdentifier(identifier, cb);
        },
        function(cb) {
          var identifier = configuration.test.fakeDocument2.identifier;
          anyfetch.deleteDocumentByIdentifier(identifier, cb);
        }
      ], done);
    });
  });

  describe('sendDocumentAndFile', function() {
    var doc = configuration.test.fakeDocument;
    var documentId;

    var hash = configuration.test.fakeImageFile;
    // Simply upload from filename
    hash.file = hash.path;

    it('should create the document', function(done) {
      anyfetch.sendDocumentAndFile(doc, hash, function(err, doc) {
        should(err).not.be.ok;
        should(doc).be.ok;
        doc.should.have.properties('id', 'identifier', 'provider', 'document_type');
        documentId = doc.id;

        done();
      });
    });

    it('should have updated the document with info from the file', function(done) {
      should(documentId).be.ok;

      // TODO: introduce delay to let the file be hydrated?
      anyfetch.getDocumentById(documentId, function(err, res) {
        var doc = res.body;
        should(err).not.be.ok;
        should(doc).be.ok;
        doc.should.have.properties('data');
        doc.data.should.have.properties({ 'extension': 'jpg' });

        done();
      });

    });

    after(function(done) {
      anyfetch.deleteDocumentById(documentId, done);
    });
  });

  describe('getCurrentUser', function() {
    it('should get the correct user', function(done) {
      anyfetch.getCurrentUser(function(err, user) {
        should(err).not.be.ok;
        should(user).be.ok;
        should(user).have.properties('id', 'email', 'name', 'is_admin');
        user.email.should.eql(configuration.test.login);

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

  describe('createSubcompanyWithAdmin', function() {
    var admin = configuration.test.fakeUser;
    var subcompany = configuration.test.fakeCompany;
    var subcompanyId;

    it('should run smoothly', function(done) {
      anyfetch.createSubcompanyWithAdmin(subcompany, admin, function(err, company) {
        subcompanyId = company.id;
        done(err);
      });
    });

    it('should have created the subcompany', function(done) {
      should(subcompanyId).be.ok;

      anyfetch.getSubcompanyById(subcompanyId, function(err, res) {
        var subcompany = res.body;
        should(err).not.be.ok;
        should(subcompany).be.ok;
        subcompany.should.have.property('id').and.equal(subcompanyId);

        done();
      });
    });

    it('should have created the new user and moved it to the subcompany', function(done) {
      var newAdminFetch = new Anyfetch(admin.email, admin.password);
      newAdminFetch.getCompany(function(err, res) {
        var company = res.body;
        should(err).not.be.ok;
        should(company).be.ok;
        company.should.have.property('id').and.equal(subcompanyId);

        done();
      });
    });

    after(function(done) {
      anyfetch.deleteSubcompanyById(subcompanyId, done);
    });
  });

});
<commit_msg>Do not rely on hydrater's speed<commit_after>'use strict';

var should = require('should');
var async = require('async');

var Anyfetch = require('../lib/index.js');
var configuration = require('../config/configuration.js');

// TODO: use mock server

describe('<High-level helper functions>', function() {
  var anyfetch;
  var anyfetchBasic = new Anyfetch(configuration.test.login, configuration.test.password);

  // Retrieve token from credentials
  before(function(done) {
    anyfetchBasic.getToken(function(err, res) {
      anyfetch = new Anyfetch(res.body.token);
      done(err);
    });
  });

  describe('getDocumentWithInfo', function() {
    var documentId;

    before(function(done) {
      anyfetch.postDocument(configuration.test.fakeDocument, function(err, res) {
        if(res.body && res.body.id) {
          documentId = res.body.id;
        }
        done(err);
      });
    });

    it('should get document and populate `document_type` and `provider`', function(done) {
      anyfetch.getDocumentWithInfo(documentId, function(err, doc) {
        should(err).not.be.ok;
        should(doc).be.ok;
        doc.should.have.properties('id', 'identifier', 'provider', 'document_type');
        doc.provider.should.have.properties('client', 'name', 'document_count');
        doc.document_type.should.have.properties('id', 'name', 'templates');

        done();
      });
    });

    it('same thing by identifier', function(done) {
      var identifier = configuration.test.fakeDocument.identifier;
      anyfetch.getDocumentByIdentifierWithInfo(identifier, function(err, doc) {
        should(err).not.be.ok;
        should(doc).be.ok;
        doc.should.have.properties('id', 'identifier', 'provider', 'document_type');
        doc.provider.should.have.properties('client', 'name', 'document_count');
        doc.document_type.should.have.properties('id', 'name', 'templates');

        done();
      });
    });

    after(function(done) {
      anyfetch.deleteDocumentById(documentId, done);
    });
  });

  describe('getDocumentsWithInfo', function() {

    // Prepare two documents
    before(function(done) {
      async.parallel([
        function(cb) {
          anyfetch.postDocument(configuration.test.fakeDocument, cb);
        },
        function(cb) {
          anyfetch.postDocument(configuration.test.fakeDocument2, cb);
        }
      ], done);
    });

    it('should get all documents and populate their `document_type` and `provider`', function(done) {
      anyfetch.getDocumentsWithInfo(function(err, docs) {
        docs.data.forEach(function(doc) {
          should(err).not.be.ok;
          should(doc).be.ok;
          doc.should.have.properties('id', 'identifier', 'provider', 'document_type');
          doc.provider.should.have.properties('client', 'name', 'document_count');
          doc.document_type.should.have.properties('id', 'name', 'templates');
        });

        done();
      });
    });

    after(function(done) {
      async.parallel([
        function(cb) {
          var identifier = configuration.test.fakeDocument.identifier;
          anyfetch.deleteDocumentByIdentifier(identifier, cb);
        },
        function(cb) {
          var identifier = configuration.test.fakeDocument2.identifier;
          anyfetch.deleteDocumentByIdentifier(identifier, cb);
        }
      ], done);
    });
  });

  describe('sendDocumentAndFile', function() {
    var doc = configuration.test.fakeDocument;
    var documentId;

    var hash = configuration.test.fakeImageFile;
    // Simply upload from filename
    hash.file = hash.path;

    it('should create the document and post the file without error', function(done) {
      anyfetch.sendDocumentAndFile(doc, hash, function(err, doc) {
        should(err).not.be.ok;
        should(doc).be.ok;
        doc.should.have.properties('id', 'identifier', 'provider', 'document_type');
        documentId = doc.id;

        done();
      });
    });

    after(function(done) {
      anyfetch.deleteDocumentById(documentId, done);
    });
  });

  describe('getCurrentUser', function() {
    it('should get the correct user', function(done) {
      anyfetch.getCurrentUser(function(err, user) {
        should(err).not.be.ok;
        should(user).be.ok;
        should(user).have.properties('id', 'email', 'name', 'is_admin');
        user.email.should.eql(configuration.test.login);

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

  describe('createSubcompanyWithAdmin', function() {
    var admin = configuration.test.fakeUser;
    var subcompany = configuration.test.fakeCompany;
    var subcompanyId;

    it('should run smoothly', function(done) {
      anyfetch.createSubcompanyWithAdmin(subcompany, admin, function(err, company) {
        subcompanyId = company.id;
        done(err);
      });
    });

    it('should have created the subcompany', function(done) {
      should(subcompanyId).be.ok;

      anyfetch.getSubcompanyById(subcompanyId, function(err, res) {
        var subcompany = res.body;
        should(err).not.be.ok;
        should(subcompany).be.ok;
        subcompany.should.have.property('id').and.equal(subcompanyId);

        done();
      });
    });

    it('should have created the new user and moved it to the subcompany', function(done) {
      var newAdminFetch = new Anyfetch(admin.email, admin.password);
      newAdminFetch.getCompany(function(err, res) {
        var company = res.body;
        should(err).not.be.ok;
        should(company).be.ok;
        company.should.have.property('id').and.equal(subcompanyId);

        done();
      });
    });

    after(function(done) {
      anyfetch.deleteSubcompanyById(subcompanyId, done);
    });
  });

});
<|endoftext|>


<commit_before>(function(root, factory) {
    if (typeof exports !== "undefined") {
        var React = require("react");
        var PropTypes = require("prop-types");
        var createReactClass = require("create-react-class");
        module.exports = factory(React, PropTypes, createReactClass);
    }
    else if (typeof define === "function" && define.amd) {
        define(["react", "prop-types", "create-react-class"], function(React, PropTypes, createReactClass) {
            return factory(React, PropTypes, createReactClass);
        });
    }
    else {
        factory(root.React, root.PropTypes, root.createReactClass);
    }
}(this, function(React, PropTypes, createReactClass) {

    var FileDrop = createReactClass({
        displayName: "FileDrop",

        propTypes: {
            onDrop: PropTypes.func,
            onDragOver: PropTypes.func,
            onDragLeave: PropTypes.func,
            dropEffect: PropTypes.oneOf(["copy", "move", "link", "none"]),
            targetAlwaysVisible: PropTypes.bool,
            frame: function (props, propName, componentName) {
                var prop = props[propName];
                if (prop == null) {
                    return new Error("Warning: Required prop `" + propName + "` was not specified in `" + componentName + "`");
                }
                if (prop !== document && prop !== window && !(prop instanceof HTMLElement)) {
                    return new Error("Warning: Prop `" + propName + "` must be one of the following: document, window, or an HTMLElement!");
                }
            },
            onFrameDragEnter: PropTypes.func,
            onFrameDragLeave: PropTypes.func,
            onFrameDrop: PropTypes.func
        },

        getDefaultProps: function () {
            return {
                dropEffect: "copy",
                frame: document,
                targetAlwaysVisible: false
            };
        },

        // getInitialState: in componentWillMount, we call this.resetDragging();

        resetDragging: function () {
            this._dragCount = 0;
            this.setState({draggingOverFrame: false, draggingOverTarget: false});
        },

        _handleDrop: function (event) {
            event.preventDefault();
            if (this.props.onDrop) {
                var files = (event.dataTransfer) ? event.dataTransfer.files : (event.frame) ? event.frame.files : undefined;
                this.props.onDrop(files, event);
                this.resetDragging();
            }
        },

        _handleDragOver: function (event) {
            event.preventDefault();
            event.stopPropagation();
            event.dataTransfer.dropEffect = this.props.dropEffect;

            // set active drag state only when file is dragged into
            // (in mozilla when file is dragged effect is "uninitialized")
            var effectAllowed = event.dataTransfer.effectAllowed;
            if (effectAllowed === "all" || effectAllowed === "uninitialized") {
                this.setState({draggingOverTarget: true});
            }

            if (this.props.onDragOver) this.props.onDragOver(event);
        },

        _handleDragLeave: function (event) {
            this.setState({draggingOverTarget: false});
            if (this.props.onDragLeave) this.props.onDragLeave(event);
        },

        _handleFrameDrag: function (event) {
            // We are listening for events on the 'frame', so every time the user drags over any element in the frame's tree,
            // the event bubbles up to the frame. By keeping count of how many "dragenters" we get, we can tell if they are still
            // "draggingOverFrame" (b/c you get one "dragenter" initially, and one "dragenter"/one "dragleave" for every bubble)
            this._dragCount += (event.type === "dragenter" ? 1 : -1);
            if (this._dragCount === 1) {
                if (this.props.onFrameDragEnter) {
					if (this.props.onFrameDragEnter(event) === false) {
						return;
					}
				}
                this.setState({draggingOverFrame: true});
            } else if (this._dragCount === 0) {
                if (this.props.onFrameDragLeave) this.props.onFrameDragLeave(event);
                this.setState({draggingOverFrame: false});
            }
        },

        _handleFrameDrop: function(event) {
            if (!this.state.draggingOverTarget) {
                this.resetDragging();
                if (this.props.onFrameDrop) this.props.onFrameDrop(event);
            }
            this._handleDrop(event);
        },

        render: function () {
            var fileDropTarget;
            var fileDropTargetClassName = "file-drop-target";
            if (this.props.targetAlwaysVisible || this.state.draggingOverFrame) {
                if (this.state.draggingOverFrame) fileDropTargetClassName += " file-drop-dragging-over-frame";
                if (this.state.draggingOverTarget) fileDropTargetClassName += " file-drop-dragging-over-target";
                fileDropTarget = (
                    React.createElement("div", {className: fileDropTargetClassName},
                        this.props.children
                    )
                );
            }
            var className = "file-drop";
            if (this.props.className) {
                className += " " + this.props.className;
            }
            return (
                React.createElement("div", {className: className, onDrop: this._handleDrop, onDragLeave: this._handleDragLeave, onDragOver: this._handleDragOver},
                    fileDropTarget
                )
            );
        },

        _handleWindowDragOverOrDrop: function(event) {
            event.preventDefault();
        },

        componentWillReceiveProps: function(nextProps) {
            if (nextProps.frame !== this.props.frame) {
                this.resetDragging();
                this.stopFrameListeners(this.props.frame);
                this.startFrameListeners(nextProps.frame);
            }
        },

        componentWillMount: function() {
            this.startFrameListeners();
            this.resetDragging();
            window.addEventListener("dragover", this._handleWindowDragOverOrDrop);
            window.addEventListener("drop", this._handleWindowDragOverOrDrop);
        },

        componentWillUnmount: function() {
            this.stopFrameListeners();
            window.removeEventListener("dragover", this._handleWindowDragOverOrDrop);
            window.removeEventListener("drop", this._handleWindowDragOverOrDrop);
        },

        stopFrameListeners: function(frame) {
            frame = frame || this.props.frame;
            frame.removeEventListener("dragenter", this._handleFrameDrag);
            frame.removeEventListener("dragleave", this._handleFrameDrag);
            frame.removeEventListener("drop", this._handleFrameDrop);
        },

        startFrameListeners: function(frame) {
            frame = frame || this.props.frame;
            frame.addEventListener("dragenter", this._handleFrameDrag);
            frame.addEventListener("dragleave", this._handleFrameDrag);
            frame.addEventListener("drop", this._handleFrameDrop);
        }
    });

    if (typeof exports === "undefined" && typeof define !== "function" && !this.ReactFileDrop) {
        this.ReactFileDrop = FileDrop;
    }

    return FileDrop;
}));
<commit_msg>Removed drop event on frame. Drop event would trigger twice, once on target and again on frame.<commit_after>(function(root, factory) {
    if (typeof exports !== "undefined") {
        var React = require("react");
        var PropTypes = require("prop-types");
        var createReactClass = require("create-react-class");
        module.exports = factory(React, PropTypes, createReactClass);
    }
    else if (typeof define === "function" && define.amd) {
        define(["react", "prop-types", "create-react-class"], function(React, PropTypes, createReactClass) {
            return factory(React, PropTypes, createReactClass);
        });
    }
    else {
        factory(root.React, root.PropTypes, root.createReactClass);
    }
}(this, function(React, PropTypes, createReactClass) {

    var FileDrop = createReactClass({
        displayName: "FileDrop",

        propTypes: {
            onDrop: PropTypes.func,
            onDragOver: PropTypes.func,
            onDragLeave: PropTypes.func,
            dropEffect: PropTypes.oneOf(["copy", "move", "link", "none"]),
            targetAlwaysVisible: PropTypes.bool,
            frame: function (props, propName, componentName) {
                var prop = props[propName];
                if (prop == null) {
                    return new Error("Warning: Required prop `" + propName + "` was not specified in `" + componentName + "`");
                }
                if (prop !== document && prop !== window && !(prop instanceof HTMLElement)) {
                    return new Error("Warning: Prop `" + propName + "` must be one of the following: document, window, or an HTMLElement!");
                }
            },
            onFrameDragEnter: PropTypes.func,
            onFrameDragLeave: PropTypes.func,
            onFrameDrop: PropTypes.func
        },

        getDefaultProps: function () {
            return {
                dropEffect: "copy",
                frame: document,
                targetAlwaysVisible: false
            };
        },

        // getInitialState: in componentWillMount, we call this.resetDragging();

        resetDragging: function () {
            this._dragCount = 0;
            this.setState({draggingOverFrame: false, draggingOverTarget: false});
        },

        _handleDrop: function (event) {
            event.preventDefault();
            if (this.props.onDrop) {
                var files = (event.dataTransfer) ? event.dataTransfer.files : (event.frame) ? event.frame.files : undefined;
                this.props.onDrop(files, event);
                this.resetDragging();
            }
        },

        _handleDragOver: function (event) {
            event.preventDefault();
            event.stopPropagation();
            event.dataTransfer.dropEffect = this.props.dropEffect;

            // set active drag state only when file is dragged into
            // (in mozilla when file is dragged effect is "uninitialized")
            var effectAllowed = event.dataTransfer.effectAllowed;
            if (effectAllowed === "all" || effectAllowed === "uninitialized") {
                this.setState({draggingOverTarget: true});
            }

            if (this.props.onDragOver) this.props.onDragOver(event);
        },

        _handleDragLeave: function (event) {
            this.setState({draggingOverTarget: false});
            if (this.props.onDragLeave) this.props.onDragLeave(event);
        },

        _handleFrameDrag: function (event) {
            // We are listening for events on the 'frame', so every time the user drags over any element in the frame's tree,
            // the event bubbles up to the frame. By keeping count of how many "dragenters" we get, we can tell if they are still
            // "draggingOverFrame" (b/c you get one "dragenter" initially, and one "dragenter"/one "dragleave" for every bubble)
            this._dragCount += (event.type === "dragenter" ? 1 : -1);
            if (this._dragCount === 1) {
                if (this.props.onFrameDragEnter) {
					if (this.props.onFrameDragEnter(event) === false) {
						return;
					}
				}
                this.setState({draggingOverFrame: true});
            } else if (this._dragCount === 0) {
                if (this.props.onFrameDragLeave) this.props.onFrameDragLeave(event);
                this.setState({draggingOverFrame: false});
            }
        },

        _handleFrameDrop: function(event) {
            if (!this.state.draggingOverTarget) {
                this.resetDragging();
                if (this.props.onFrameDrop) this.props.onFrameDrop(event);
            }
        },

        render: function () {
            var fileDropTarget;
            var fileDropTargetClassName = "file-drop-target";
            if (this.props.targetAlwaysVisible || this.state.draggingOverFrame) {
                if (this.state.draggingOverFrame) fileDropTargetClassName += " file-drop-dragging-over-frame";
                if (this.state.draggingOverTarget) fileDropTargetClassName += " file-drop-dragging-over-target";
                fileDropTarget = (
                    React.createElement("div", {className: fileDropTargetClassName},
                        this.props.children
                    )
                );
            }
            var className = "file-drop";
            if (this.props.className) {
                className += " " + this.props.className;
            }
            return (
                React.createElement("div", {className: className, onDrop: this._handleDrop, onDragLeave: this._handleDragLeave, onDragOver: this._handleDragOver},
                    fileDropTarget
                )
            );
        },

        _handleWindowDragOverOrDrop: function(event) {
            event.preventDefault();
        },

        componentWillReceiveProps: function(nextProps) {
            if (nextProps.frame !== this.props.frame) {
                this.resetDragging();
                this.stopFrameListeners(this.props.frame);
                this.startFrameListeners(nextProps.frame);
            }
        },

        componentWillMount: function() {
            this.startFrameListeners();
            this.resetDragging();
            window.addEventListener("dragover", this._handleWindowDragOverOrDrop);
            window.addEventListener("drop", this._handleWindowDragOverOrDrop);
        },

        componentWillUnmount: function() {
            this.stopFrameListeners();
            window.removeEventListener("dragover", this._handleWindowDragOverOrDrop);
            window.removeEventListener("drop", this._handleWindowDragOverOrDrop);
        },

        stopFrameListeners: function(frame) {
            frame = frame || this.props.frame;
            frame.removeEventListener("dragenter", this._handleFrameDrag);
            frame.removeEventListener("dragleave", this._handleFrameDrag);
            frame.removeEventListener("drop", this._handleFrameDrop);
        },

        startFrameListeners: function(frame) {
            frame = frame || this.props.frame;
            frame.addEventListener("dragenter", this._handleFrameDrag);
            frame.addEventListener("dragleave", this._handleFrameDrag);
            frame.addEventListener("drop", this._handleFrameDrop);
        }
    });

    if (typeof exports === "undefined" && typeof define !== "function" && !this.ReactFileDrop) {
        this.ReactFileDrop = FileDrop;
    }

    return FileDrop;
}));
<|endoftext|>


<commit_before>/* global describe, it*/
'use strict';

const path = require('path');
const stream = require('stream');
const eslint = require('..');
const File = require('vinyl');
const from2string = require('from2-string');
const should = require('should');
const BufferStreams = require('bufferstreams');

require('mocha');

describe('gulp-eslint plugin', () => {

	it('should configure an alternate parser', done => {
		eslint({
			parser: 'babel-eslint',
			useEslintrc: false,
			rules: {'arrow-parens': 2}
		})
		.on('error', done)
		.on('data', file => {
			should.exist(file);
			should.exist(file.contents);
			should.exist(file.eslint);
			file.eslint.should.have.property('filePath', path.resolve('test/fixtures/es6.js'));

			file.eslint.messages
			.should.be.instanceof(Array)
			.and.have.lengthOf(1);

			file.eslint.messages[0]
			.should.have.properties('message', 'line', 'column')
			.and.have.property('ruleId', 'arrow-parens');

			done();
		})
		.end(new File({
			path: 'test/fixtures/es6.js',
			contents: new Buffer('(a=>`${a}b`)("a");')
		}));
	});

	it('should produce expected message via buffer', done => {
		eslint({useEslintrc: false, rules: {strict: [2, 'global']}})
		.on('error', done)
		.on('data', file => {
			should.exist(file);
			should.exist(file.contents);
			should.exist(file.eslint);
			file.eslint.should.have.property('filePath', path.resolve('test/fixtures/use-strict.js'));

			file.eslint.messages
			.should.be.instanceof(Array)
			.and.have.lengthOf(1);

			file.eslint.messages[0]
			.should.have.properties('message', 'line', 'column')
			.and.have.property('ruleId', 'strict');

			done();
		})
		.end(new File({
			path: 'test/fixtures/use-strict.js',
			contents: new Buffer('var x = 1;')
		}));
	});

	it('should produce expected message upon stream completion', done => {
		eslint({useEslintrc: false, rules: {strict: [2, 'global']}})
		.on('error', done)
		.on('data', file => {
			should.exist(file);
			should.ok(file.isStream());

			file.contents
			.on('error', done)
			.on('data', () => {
				should.exist(file.eslint);

				file.eslint.messages
				.should.be.instanceof(Array)
				.and.have.lengthOf(1);

				file.eslint.messages[0]
				.should.have.properties('message', 'line', 'column')
				.and.have.property('ruleId', 'strict');

				done();
			});
		})
		.end(new File({
			cwd: 'test/',
			base: 'test/fixtures',
			path: 'test/fixtures/use-strict.js',
			contents: from2string('(function () { void 0; })();')
		}));
	});

	it('should lint multiple streaming files', done => {
		let fileCount = 0;

		const lintStream = eslint({useEslintrc: false, rules: {strict: [2, 'global']}})
		.on('error', done)
		.on('data', file => {
			should.exist(file);
			should.ok(file.isStream());

			should.exist(file.contents);
			should.ok(file.contents.readable);

			should.exist(file.eslint);
			file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(1);
			file.eslint.messages[0]
			.should.have.properties('message', 'line', 'column')
			.and.have.property('ruleId', 'strict');

			fileCount++;
		});

		lintStream.pipe(new stream.PassThrough({objectMode: true}))
		.on('finish', () => {
			fileCount.should.equal(2);
			done();
		});

		lintStream.write(new File({
			path: 'test/fixtures/use-strict.js',
			contents: from2string('(function () { void 0; })();')
		}));
		lintStream.write(new File({
			path: 'test/fixtures/use-strict.js',
			contents: from2string('(function () { void 0; })();')
		}));

		lintStream.end();
	});

	it('should ignore files with null content', done => {
		eslint({useEslintrc: false, rules: {'strict': 2}})
		.on('error', done)
		.on('data', file => {
			should.exist(file);
			should.not.exist(file.contents);
			should.not.exist(file.eslint);
			done();
		})
		.end(new File({
			path: 'test/fixtures',
			isDirectory: true
		}));
	});

	describe('"warnFileIgnored" option', () => {

		it('when true, should warn when a file is ignored by .eslintignore', done => {
			eslint({useEslintrc: false, warnFileIgnored: true})
			.on('error', done)
			.on('data', file => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(1);
				file.eslint.messages[0]
				.should.have.property('message', 'File ignored because of .eslintignore file');
				file.eslint.errorCount.should.equal(0);
				file.eslint.warningCount.should.equal(1);
				done();
			})
			.end(new File({
				path: 'test/fixtures/ignored.js',
				contents: new Buffer('(function () {ignore = abc;}});')
			}));
		});

		it('when true, should warn when a "node_modules" file is ignored', done => {
			eslint({useEslintrc: false, warnFileIgnored: true})
			.on('error', done)
			.on('data', file => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(1);
				file.eslint.messages[0].should.have.property('message',
					'File ignored because it has a node_modules/** path');
				file.eslint.errorCount.should.equal(0);
				file.eslint.warningCount.should.equal(1);
				done();
			})
			.end(new File({
				path: 'node_modules/test/index.js',
				contents: new Buffer('(function () {ignore = abc;}});')
			}));
		});

		it('when not true, should silently ignore files', done => {
			eslint({useEslintrc: false, warnFileIgnored: false})
			.on('error', done)
			.on('data', file => {
				should.exist(file);
				should.not.exist(file.eslint);
				done();
			})
			.end(new File({
				path: 'test/fixtures/ignored.js',
				contents: new Buffer('(function () {ignore = abc;}});')
			}));
		});

	});

	describe('"quiet" option', () => {

		it('when true, should remove warnings', done => {
			eslint({quiet: true, useEslintrc: false, rules: {'no-undef': 1, 'strict': 2}})
			.on('data', file => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(1);
				file.eslint.errorCount.should.equal(1);
				file.eslint.warningCount.should.equal(0);
				done();
			})
			.end(new File({
				path: 'test/fixtures/invalid.js',
				contents: new Buffer('function z() { x = 0; }')
			}));
		});

		it('when a function, should filter messages', done => {
			function warningsOnly(message) {
				return message.severity === 1;
			}
			eslint({quiet: warningsOnly, useEslintrc: false, rules: {'no-undef': 1, 'strict': 2}})
			.on('data', file => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(1);
				file.eslint.errorCount.should.equal(0);
				file.eslint.warningCount.should.equal(1);
				done();
			})
			.end(new File({
				path: 'test/fixtures/invalid.js',
				contents: new Buffer('function z() { x = 0; }')
			}));
		});

	});

	describe('"fix" option', () => {

		it('when true, should update buffered contents', done => {
			eslint({fix: true, useEslintrc: false, rules: {'no-trailing-spaces': 2}})
			.on('error', done)
			.on('data', (file) => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(0);
				file.eslint.errorCount.should.equal(0);
				file.eslint.warningCount.should.equal(0);
				file.eslint.output.should.equal('var x = 0;');
				file.contents.toString().should.equal('var x = 0;');
				done();
			})
			.end(new File({
				path: 'test/fixtures/fixable.js',
				contents: new Buffer('var x = 0; ')
			}));
		});

		it('when true, should update stream contents', done => {
			eslint({fix: true, useEslintrc: false, rules: {'no-trailing-spaces': 2}})
			.on('error', done)
			.on('data', (file) => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(0);
				file.eslint.errorCount.should.equal(0);
				file.eslint.warningCount.should.equal(0);
				file.eslint.output.should.equal('var x = 0;;');
				file.contents = file.contents.pipe(new BufferStreams((err, buf, cb) => {
					cb(err, buf);
					buf.toString().should.equal('var x = 0;;');
					done();
				}));

			})
			.end(new File({
				path: 'test/fixtures/fixable.js',
				contents: from2string('var x = 0;;  ')
			}));
		});

	});

});
<commit_msg>test the case where babel-eslint is actually useful<commit_after>/* global describe, it*/
'use strict';

const path = require('path');
const stream = require('stream');
const eslint = require('..');
const File = require('vinyl');
const from2string = require('from2-string');
const should = require('should');
const BufferStreams = require('bufferstreams');

require('mocha');

describe('gulp-eslint plugin', () => {
	it('should configure an alternate parser', done => {
		eslint({
			parser: 'babel-eslint',
			useEslintrc: false,
			rules: {'prefer-template': 'error'}
		})
		.on('error', done)
		.on('data', file => {
			should.exist(file);
			should.exist(file.contents);
			should.exist(file.eslint);
			file.eslint.should.have.property('filePath', path.resolve('test/fixtures/stage0-class-property.js'));

			file.eslint.messages
			.should.be.instanceof(Array)
			.and.have.lengthOf(1);

			file.eslint.messages[0]
			.should.have.properties('message', 'line', 'column')
			.and.have.property('ruleId', 'prefer-template');

			done();
		})
		.end(new File({
			path: 'test/fixtures/stage0-class-property.js',
			contents: new Buffer('class MyClass {prop = a + "b" + c;}')
		}));
	});

	it('should produce expected message via buffer', done => {
		eslint({useEslintrc: false, rules: {strict: [2, 'global']}})
		.on('error', done)
		.on('data', file => {
			should.exist(file);
			should.exist(file.contents);
			should.exist(file.eslint);
			file.eslint.should.have.property('filePath', path.resolve('test/fixtures/use-strict.js'));

			file.eslint.messages
			.should.be.instanceof(Array)
			.and.have.lengthOf(1);

			file.eslint.messages[0]
			.should.have.properties('message', 'line', 'column')
			.and.have.property('ruleId', 'strict');

			done();
		})
		.end(new File({
			path: 'test/fixtures/use-strict.js',
			contents: new Buffer('var x = 1;')
		}));
	});

	it('should produce expected message upon stream completion', done => {
		eslint({useEslintrc: false, rules: {strict: [2, 'global']}})
		.on('error', done)
		.on('data', file => {
			should.exist(file);
			should.ok(file.isStream());

			file.contents
			.on('error', done)
			.on('data', () => {
				should.exist(file.eslint);

				file.eslint.messages
				.should.be.instanceof(Array)
				.and.have.lengthOf(1);

				file.eslint.messages[0]
				.should.have.properties('message', 'line', 'column')
				.and.have.property('ruleId', 'strict');

				done();
			});
		})
		.end(new File({
			cwd: 'test/',
			base: 'test/fixtures',
			path: 'test/fixtures/use-strict.js',
			contents: from2string('(function () { void 0; })();')
		}));
	});

	it('should lint multiple streaming files', done => {
		let fileCount = 0;

		const lintStream = eslint({useEslintrc: false, rules: {strict: [2, 'global']}})
		.on('error', done)
		.on('data', file => {
			should.exist(file);
			should.ok(file.isStream());

			should.exist(file.contents);
			should.ok(file.contents.readable);

			should.exist(file.eslint);
			file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(1);
			file.eslint.messages[0]
			.should.have.properties('message', 'line', 'column')
			.and.have.property('ruleId', 'strict');

			fileCount++;
		});

		lintStream.pipe(new stream.PassThrough({objectMode: true}))
		.on('finish', () => {
			fileCount.should.equal(2);
			done();
		});

		lintStream.write(new File({
			path: 'test/fixtures/use-strict.js',
			contents: from2string('(function () { void 0; })();')
		}));
		lintStream.write(new File({
			path: 'test/fixtures/use-strict.js',
			contents: from2string('(function () { void 0; })();')
		}));

		lintStream.end();
	});

	it('should ignore files with null content', done => {
		eslint({useEslintrc: false, rules: {'strict': 2}})
		.on('error', done)
		.on('data', file => {
			should.exist(file);
			should.not.exist(file.contents);
			should.not.exist(file.eslint);
			done();
		})
		.end(new File({
			path: 'test/fixtures',
			isDirectory: true
		}));
	});

	describe('"warnFileIgnored" option', () => {

		it('when true, should warn when a file is ignored by .eslintignore', done => {
			eslint({useEslintrc: false, warnFileIgnored: true})
			.on('error', done)
			.on('data', file => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(1);
				file.eslint.messages[0]
				.should.have.property('message', 'File ignored because of .eslintignore file');
				file.eslint.errorCount.should.equal(0);
				file.eslint.warningCount.should.equal(1);
				done();
			})
			.end(new File({
				path: 'test/fixtures/ignored.js',
				contents: new Buffer('(function () {ignore = abc;}});')
			}));
		});

		it('when true, should warn when a "node_modules" file is ignored', done => {
			eslint({useEslintrc: false, warnFileIgnored: true})
			.on('error', done)
			.on('data', file => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(1);
				file.eslint.messages[0].should.have.property('message',
					'File ignored because it has a node_modules/** path');
				file.eslint.errorCount.should.equal(0);
				file.eslint.warningCount.should.equal(1);
				done();
			})
			.end(new File({
				path: 'node_modules/test/index.js',
				contents: new Buffer('(function () {ignore = abc;}});')
			}));
		});

		it('when not true, should silently ignore files', done => {
			eslint({useEslintrc: false, warnFileIgnored: false})
			.on('error', done)
			.on('data', file => {
				should.exist(file);
				should.not.exist(file.eslint);
				done();
			})
			.end(new File({
				path: 'test/fixtures/ignored.js',
				contents: new Buffer('(function () {ignore = abc;}});')
			}));
		});

	});

	describe('"quiet" option', () => {

		it('when true, should remove warnings', done => {
			eslint({quiet: true, useEslintrc: false, rules: {'no-undef': 1, 'strict': 2}})
			.on('data', file => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(1);
				file.eslint.errorCount.should.equal(1);
				file.eslint.warningCount.should.equal(0);
				done();
			})
			.end(new File({
				path: 'test/fixtures/invalid.js',
				contents: new Buffer('function z() { x = 0; }')
			}));
		});

		it('when a function, should filter messages', done => {
			function warningsOnly(message) {
				return message.severity === 1;
			}
			eslint({quiet: warningsOnly, useEslintrc: false, rules: {'no-undef': 1, 'strict': 2}})
			.on('data', file => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(1);
				file.eslint.errorCount.should.equal(0);
				file.eslint.warningCount.should.equal(1);
				done();
			})
			.end(new File({
				path: 'test/fixtures/invalid.js',
				contents: new Buffer('function z() { x = 0; }')
			}));
		});

	});

	describe('"fix" option', () => {

		it('when true, should update buffered contents', done => {
			eslint({fix: true, useEslintrc: false, rules: {'no-trailing-spaces': 2}})
			.on('error', done)
			.on('data', (file) => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(0);
				file.eslint.errorCount.should.equal(0);
				file.eslint.warningCount.should.equal(0);
				file.eslint.output.should.equal('var x = 0;');
				file.contents.toString().should.equal('var x = 0;');
				done();
			})
			.end(new File({
				path: 'test/fixtures/fixable.js',
				contents: new Buffer('var x = 0; ')
			}));
		});

		it('when true, should update stream contents', done => {
			eslint({fix: true, useEslintrc: false, rules: {'no-trailing-spaces': 2}})
			.on('error', done)
			.on('data', (file) => {
				should.exist(file);
				should.exist(file.eslint);
				file.eslint.messages.should.be.instanceof(Array).and.have.lengthOf(0);
				file.eslint.errorCount.should.equal(0);
				file.eslint.warningCount.should.equal(0);
				file.eslint.output.should.equal('var x = 0;;');
				file.contents = file.contents.pipe(new BufferStreams((err, buf, cb) => {
					cb(err, buf);
					buf.toString().should.equal('var x = 0;;');
					done();
				}));

			})
			.end(new File({
				path: 'test/fixtures/fixable.js',
				contents: from2string('var x = 0;;  ')
			}));
		});

	});

});
<|endoftext|>