<commit_before>/**
 * This kind provide a widget to tune project properties (phonegap
 * stuff included).
 *
 * By default, this widget is tuned for project modification.  In case
 * of project *creation*, the method setupCreate must be called after
 * construction. Since the widget is re-used between call for creation
 * or modification, the methos setupModif must be called also.
 */

enyo.kind({
	name: "ProjectProperties",
	classes: "enyo-unselectable",
	fit: true,
	events: {
		onModifiedConfig: "",
		onSaveGeneratedXml: "",
		onDone: ""
	},
	createMode: true,

	components: [
		{kind: "onyx.RadioGroup", onActivate: "switchDrawers", name: "thumbnail", components: [
			{content: "Project", active: true, attributes: {title: 'project attributes...'}},
			{content: "PhoneGap", attributes: {title: 'phonegap build parameters...'}}
		]},
		{name: "projectDrawer", kind: "onyx.Drawer", open:true, components: [
			{tag: 'table', components: [
				{tag: "tr" , components: [
					 {tag: "td" , content: "Name: "},
					 {tag: 'td', components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", defaultFocus: true, name: "projectName"}
						  ]}
					 ]},
					 {tag: 'td', content: "Title: "},
					 {tag: "td" , components: [
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", defaultFocus: true, name: "projectTitle"}
						   ]}
					  ]}
				]},
				{tag: "tr" , components: [
					 {tag: "td" , content: "Version: "},
					 {tag: 'td', components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", defaultFocus: true, name: "projectVersion"}
						  ]}
					 ]},
					 {tag: 'td', content: "Id: "},
					 {tag: "td" , components: [
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", defaultFocus: true, name: "projectId",
								attributes: {title: "Application ID in reverse domain-name format: com.example.apps.myapp"}}
						   ]}
					  ]}
				]},
				{tag: "tr" , components: [
					 {tag: "td" , content: "Author name: "},
					 {tag: 'td', attributes: {colspan: 1}, components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", name: "projectAuthor",
								attributes: {title: "Vendor / Committer Name"}
							   }
						   ]}
					 ]},
					 {tag: "td" , content: "Contact: "},
					 {tag: 'td', attributes: {colspan: 2}, components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", name: "projectContact",
								attributes: {title: "mail address or home page of the author"}
							   }
						   ]}
					 ]}
				]},
				{tag: "tr" , name:'directoryEntry', canGenerate:false, components: [
					 {tag: "td", content: "Directory: "},
					 {tag: 'td', attributes: {colspan: 3}, content: "", name: "projectDirectory" }
				]}
			]}
		]},

		{name: "phoneGapDrawer", kind: "onyx.Drawer", open: false, components: [
			{
				kind: "onyx.ToggleButton",
				name: 'pgConfEnabled',
				onContent: "enabled",
				offContent: "disabled",
				style: "margin-top: 10px;"
			},
			{tag: 'table', components: [
				{tag: "tr" , components: [
					 {tag: "td" , content: "AppId: "},
					 {tag: 'td', attributes: {colspan: 1}, components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", name: "pgConfId", placeholder: "com.example.myapp",
								attributes: {title: "unique identifier, assigned by build.phonegap.com"}
							   }
						   ]}
					 ]},
					 {tag: "td" , content: "Icon URL: "},
					 {tag: 'td', attributes: {colspan: 2}, components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", name: "pgIconUrl",
								attributes: {title: "Relative location of the application icon. Defaults to Enyo icon."}
							   }
						   ]}
					 ]}
				]}
			]},
			{content: "Targets:"},
			{
				kind: "FittableRows", attributes: {'class': 'ares_projectView_switches'},
				onChange: "enablePhoneGap",
				components: [
					{kind: "onyx.ToggleButton", name: 'androidTarget', onContent: "Android", offContent: "Android"},
					{kind: "onyx.ToggleButton", name: 'iosTarget', onContent: "Ios", offContent: "Ios"},
					{kind: "onyx.ToggleButton", name: 'winphoneTarget', onContent: "Winphone", offContent: "Winphone"},
					{kind: "onyx.ToggleButton", name: 'blackberryTarget', onContent: "Blackberry", offContent: "Blackberry"},
					{kind: "onyx.ToggleButton", name: 'webosTarget',onContent: "Webos", offContent: "Webos"}
				]
			}
		]},

		// FIXME: there should be an HTML/CSS way to avoid using FittableStuff...
		{kind: "FittableRows", style: "margin-top: 10px; width: 100%", fit: true, components: [
			{kind: "onyx.Button", classes: "onyx-negative", content: "Cancel", ontap: "doDone"},
			{name: "ok", kind: "onyx.Button", classes: "onyx-affirmative", content: "OK", ontap: "confirmTap"}
		]},

		{kind: "Ares.ErrorPopup", name: "errorPopup", msg: "unknown error"}
	],

	debug: false,

	/**
	 * Tune the widget for project creation
	 */
	setupCreate: function() {
		//this.$.ok.setDisabled(true) ;
		this.$.directoryEntry.show() ;
	},

	/**
	 * Tune the widget for project modification
	 */
	setupModif: function() {
		//this.$.ok.setDisabled(true) ;
		this.$.directoryEntry.hide() ;
	},

	/**
	 * close one drawer and open the other depending on which radio button was tapped
	 */
	switchDrawers: function(inSender, inEvent) {
		if (inEvent.originator.active === true ) {
			if (inEvent.originator.getContent() === "Project") {
				this.$.projectDrawer.setOpen(true) ;
				this.$.phoneGapDrawer.setOpen(false) ;
			}
			else {
				this.$.projectDrawer.setOpen(false) ;
				this.$.phoneGapDrawer.setOpen(true) ;
			}
		}
	},

	// switch Phonegap to "enabled" whenever user validates a target
	enablePhoneGap: function(inSender, inEvent) {
		if (inEvent.value === true ) {
			this.$.pgConfEnabled.setValue(true) ;
		}
	},

	/**
	 * pre-fill the widget with configuration data
	 * @param {Object} config is configuration data (typically from project.json)
	 *  can be a json string or an object.
	 */
	preFill: function(inData) {
		this.config = typeof inData === 'object' ? inData : JSON.parse(inData) ;
		var pgConf ;
		var pgTarget ;

		 // avoid storing 'undefined' in there
		this.$.projectId.     setValue(this.config.id || '' );
		this.$.projectVersion.setValue(this.config.version || '' );
		this.$.projectName.   setValue(this.config.name || '' );
		this.$.projectTitle.  setValue(this.config.title || '' );
		this.$.projectAuthor. setValue(this.config.author.name || '') ;
		this.$.projectContact.setValue(this.config.author.href || '') ;

		if (this.config.build && this.config.build.phonegap) {
			pgConf = this.config.build.phonegap ;
			this.$.pgConfEnabled.setValue(pgConf.enabled);
			this.$.pgConfId.setValue(pgConf.appId || '' );
			this.$.pgIconUrl.setValue(pgConf.icon.src || '' );
			if (pgConf.targets) {
				// pgTarget is a key of object pgConf.targets
				for (pgTarget in pgConf.targets ) {
					this.$[ pgTarget + 'Target' ].setValue(pgConf.targets[pgTarget]) ;
				}
			}
		}
		return this ;
	},

	confirmTap: function(inSender, inEvent) {
		// retrieve modified values
		this.log('ok tapped') ;

		this.config.id       = this.$.projectId     .getValue();
		this.config.version  = this.$.projectVersion.getValue();
		this.config.name     = this.$.projectName   .getValue();
		this.config.title    = this.$.projectTitle  .getValue();

		this.config.author.name = this.$.projectAuthor.getValue();
		this.config.author.href = this.$.projectContact.getValue();

		this.config.build.enabled = this.$.pgConfEnabled.getValue();
		this.config.build.appId   = this.$.pgConfId.getValue();

		var pgConf = this.config.build.phonegap ;
		pgConf.icon.src = this.$.pgIconUrl.getValue();

		var tglist = ['android','ios','winphone','blackberry','webos'] ;
		for ( i in tglist) {
			this.log('copy data from ' + tglist[i] +'Target') ;
			pgConf.targets[tglist[i]] = this.$[ tglist[i] + 'Target' ].getValue() ;
		}

		// to be handled by a ProjectWizard
		this.doModifiedConfig({data: this.config}) ;

		this.doDone();

		// handled here (don't bubble)
		return true;
	}
});

<commit_msg>create missing config objects when needed<commit_after>/**
 * This kind provide a widget to tune project properties (phonegap
 * stuff included).
 *
 * By default, this widget is tuned for project modification.  In case
 * of project *creation*, the method setupCreate must be called after
 * construction. Since the widget is re-used between call for creation
 * or modification, the methos setupModif must be called also.
 */

enyo.kind({
	name: "ProjectProperties",
	classes: "enyo-unselectable",
	fit: true,
	events: {
		onModifiedConfig: "",
		onSaveGeneratedXml: "",
		onDone: ""
	},
	createMode: true,

	components: [
		{kind: "onyx.RadioGroup", onActivate: "switchDrawers", name: "thumbnail", components: [
			{content: "Project", active: true, attributes: {title: 'project attributes...'}},
			{content: "PhoneGap", attributes: {title: 'phonegap build parameters...'}}
		]},
		{name: "projectDrawer", kind: "onyx.Drawer", open:true, components: [
			{tag: 'table', components: [
				{tag: "tr" , components: [
					 {tag: "td" , content: "Name: "},
					 {tag: 'td', components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", defaultFocus: true, name: "projectName"}
						  ]}
					 ]},
					 {tag: 'td', content: "Title: "},
					 {tag: "td" , components: [
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", defaultFocus: true, name: "projectTitle"}
						   ]}
					  ]}
				]},
				{tag: "tr" , components: [
					 {tag: "td" , content: "Version: "},
					 {tag: 'td', components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", defaultFocus: true, name: "projectVersion"}
						  ]}
					 ]},
					 {tag: 'td', content: "Id: "},
					 {tag: "td" , components: [
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", defaultFocus: true, name: "projectId",
								attributes: {title: "Application ID in reverse domain-name format: com.example.apps.myapp"}}
						   ]}
					  ]}
				]},
				{tag: "tr" , components: [
					 {tag: "td" , content: "Author name: "},
					 {tag: 'td', attributes: {colspan: 1}, components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", name: "projectAuthor",
								attributes: {title: "Vendor / Committer Name"}
							   }
						   ]}
					 ]},
					 {tag: "td" , content: "Contact: "},
					 {tag: 'td', attributes: {colspan: 2}, components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", name: "projectContact",
								attributes: {title: "mail address or home page of the author"}
							   }
						   ]}
					 ]}
				]},
				{tag: "tr" , name:'directoryEntry', canGenerate:false, components: [
					 {tag: "td", content: "Directory: "},
					 {tag: 'td', attributes: {colspan: 3}, content: "", name: "projectDirectory" }
				]}
			]}
		]},

		{name: "phoneGapDrawer", kind: "onyx.Drawer", open: false, components: [
			{
				kind: "onyx.ToggleButton",
				name: 'pgConfEnabled',
				onContent: "enabled",
				offContent: "disabled",
				style: "margin-top: 10px;"
			},
			{tag: 'table', components: [
				{tag: "tr" , components: [
					 {tag: "td" , content: "AppId: "},
					 {tag: 'td', attributes: {colspan: 1}, components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", name: "pgConfId", placeholder: "com.example.myapp",
								attributes: {title: "unique identifier, assigned by build.phonegap.com"}
							   }
						   ]}
					 ]},
					 {tag: "td" , content: "Icon URL: "},
					 {tag: 'td', attributes: {colspan: 2}, components:[
						  {kind: "onyx.InputDecorator", components: [
							   {kind: "Input", name: "pgIconUrl",
								attributes: {title: "Relative location of the application icon. Defaults to Enyo icon."}
							   }
						   ]}
					 ]}
				]}
			]},
			{content: "Targets:"},
			{
				kind: "FittableRows", attributes: {'class': 'ares_projectView_switches'},
				onChange: "enablePhoneGap",
				components: [
					{kind: "onyx.ToggleButton", name: 'androidTarget', onContent: "Android", offContent: "Android"},
					{kind: "onyx.ToggleButton", name: 'iosTarget', onContent: "Ios", offContent: "Ios"},
					{kind: "onyx.ToggleButton", name: 'winphoneTarget', onContent: "Winphone", offContent: "Winphone"},
					{kind: "onyx.ToggleButton", name: 'blackberryTarget', onContent: "Blackberry", offContent: "Blackberry"},
					{kind: "onyx.ToggleButton", name: 'webosTarget',onContent: "Webos", offContent: "Webos"}
				]
			}
		]},

		// FIXME: there should be an HTML/CSS way to avoid using FittableStuff...
		{kind: "FittableRows", style: "margin-top: 10px; width: 100%", fit: true, components: [
			{kind: "onyx.Button", classes: "onyx-negative", content: "Cancel", ontap: "doDone"},
			{name: "ok", kind: "onyx.Button", classes: "onyx-affirmative", content: "OK", ontap: "confirmTap"}
		]},

		{kind: "Ares.ErrorPopup", name: "errorPopup", msg: "unknown error"}
	],

	debug: false,

	/**
	 * Tune the widget for project creation
	 */
	setupCreate: function() {
		//this.$.ok.setDisabled(true) ;
		this.$.directoryEntry.show() ;
	},

	/**
	 * Tune the widget for project modification
	 */
	setupModif: function() {
		//this.$.ok.setDisabled(true) ;
		this.$.directoryEntry.hide() ;
	},

	/**
	 * close one drawer and open the other depending on which radio button was tapped
	 */
	switchDrawers: function(inSender, inEvent) {
		if (inEvent.originator.active === true ) {
			if (inEvent.originator.getContent() === "Project") {
				this.$.projectDrawer.setOpen(true) ;
				this.$.phoneGapDrawer.setOpen(false) ;
			}
			else {
				this.$.projectDrawer.setOpen(false) ;
				this.$.phoneGapDrawer.setOpen(true) ;
			}
		}
	},

	// switch Phonegap to "enabled" whenever user validates a target
	enablePhoneGap: function(inSender, inEvent) {
		if (inEvent.value === true ) {
			this.$.pgConfEnabled.setValue(true) ;
		}
	},

	/**
	 * pre-fill the widget with configuration data
	 * @param {Object} config is configuration data (typically from project.json)
	 *  can be a json string or an object.
	 */
	preFill: function(inData) {
		this.config = typeof inData === 'object' ? inData : JSON.parse(inData) ;
		var pgConf ;
		var pgTarget ;
		var conf = this.config ;

		 // avoid storing 'undefined' in there
		this.$.projectId.     setValue(conf.id      || '' );
		this.$.projectVersion.setValue(conf.version || '' );
		this.$.projectName.   setValue(conf.name    || '' );
		this.$.projectTitle.  setValue(conf.title   || '' );

		if (! conf.author) { conf.author =  {} ;}
		this.$.projectAuthor. setValue(conf.author.name || '') ;
		this.$.projectContact.setValue(conf.author.href || '') ;

		if (! conf.build) {conf.build = {} ;}
		if (! conf.build.phonegap) { conf.build.phonegap = {}; }

		pgConf = this.config.build.phonegap ;
		this.$.pgConfEnabled.setValue(pgConf.enabled);
		this.$.pgConfId.setValue(pgConf.appId || '' );
		this.$.pgIconUrl.setValue(pgConf.icon.src || '' );

		if (! pgConf.targets) { pgConf.targets = {} ;}
		// pgTarget is a key of object pgConf.targets
		for (pgTarget in pgConf.targets ) {
			this.$[ pgTarget + 'Target' ].setValue(pgConf.targets[pgTarget]) ;
		}

		return this ;
	},

	confirmTap: function(inSender, inEvent) {
		// retrieve modified values
		this.log('ok tapped') ;

		this.config.id       = this.$.projectId     .getValue();
		this.config.version  = this.$.projectVersion.getValue();
		this.config.name     = this.$.projectName   .getValue();
		this.config.title    = this.$.projectTitle  .getValue();

		this.config.author.name = this.$.projectAuthor.getValue();
		this.config.author.href = this.$.projectContact.getValue();

		this.config.build.enabled = this.$.pgConfEnabled.getValue();
		this.config.build.appId   = this.$.pgConfId.getValue();

		var pgConf = this.config.build.phonegap ;
		pgConf.icon.src = this.$.pgIconUrl.getValue();

		var tglist = ['android','ios','winphone','blackberry','webos'] ;
		for ( i in tglist) {
			this.log('copy data from ' + tglist[i] +'Target') ;
			pgConf.targets[tglist[i]] = this.$[ tglist[i] + 'Target' ].getValue() ;
		}

		// to be handled by a ProjectWizard
		this.doModifiedConfig({data: this.config}) ;

		this.doDone();

		// handled here (don't bubble)
		return true;
	}
});

<|endoftext|>


<commit_before>import { inject as service } from '@ember/service';
import Watchable from './watchable';

export default Watchable.extend({
  system: service(),

  findAll() {
    const namespace = this.get('system.activeNamespace');
    return this._super(...arguments).then(data => {
      data.forEach(job => {
        job.Namespace = namespace ? namespace.get('id') : 'default';
      });
      return data;
    });
  },

  findRecord(store, type, id, snapshot) {
    const [, namespace] = JSON.parse(id);
    const namespaceQuery = namespace && namespace !== 'default' ? { namespace } : {};

    return this._super(store, type, id, snapshot, namespaceQuery);
  },

  urlForFindAll() {
    const url = this._super(...arguments);
    const namespace = this.get('system.activeNamespace.id');
    return associateNamespace(url, namespace);
  },

  urlForFindRecord(id, type, hash) {
    const [name, namespace] = JSON.parse(id);
    let url = this._super(name, type, hash);
    return associateNamespace(url, namespace);
  },

  urlForUpdateRecord(id, type, hash) {
    const [name, namespace] = JSON.parse(id);
    let url = this._super(name, type, hash);
    return associateNamespace(url, namespace);
  },

  xhrKey(url, method, options = {}) {
    const plainKey = this._super(...arguments);
    const namespace = options.data && options.data.namespace;
    return associateNamespace(plainKey, namespace);
  },

  relationshipFallbackLinks: {
    summary: '/summary',
  },

  fetchRawDefinition(job) {
    const url = this.urlForFindRecord(job.get('id'), 'job');
    return this.ajax(url, 'GET');
  },

  forcePeriodic(job) {
    if (job.get('periodic')) {
      const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/periodic/force');
      return this.ajax(url, 'POST');
    }
  },

  stop(job) {
    const url = this.urlForFindRecord(job.get('id'), 'job');
    return this.ajax(url, 'DELETE');
  },

  parse(spec) {
    const url = addToPath(this.urlForFindAll('job'), '/parse');
    return this.ajax(url, 'POST', {
      data: {
        JobHCL: spec,
        Canonicalize: true,
      },
    });
  },

  plan(job) {
    const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/plan');
    return this.ajax(url, 'POST', {
      data: {
        Job: job.get('_newDefinitionJSON'),
        Diff: true,
      },
    }).then(json => {
      json.ID = job.get('id');
      this.get('store').pushPayload('job-plan', { jobPlans: [json] });
    });
  },

  // Running a job doesn't follow REST create semantics so it's easier to
  // treat it as an action.
  run(job) {
    return this.ajax(this.urlForCreateRecord('job'), 'POST', {
      data: {
        Job: job.get('_newDefinitionJSON'),
      },
    });
  },

  update(job) {
    const url = this.urlForUpdateRecord(job.get('id'), 'job');
    return this.ajax(this.urlForUpdateRecord(job.get('id'), 'job'), 'POST', {
      data: {
        Job: job.get('_newDefinitionJSON'),
      },
    });
  },
});

function associateNamespace(url, namespace) {
  if (namespace && namespace !== 'default') {
    url += `?namespace=${namespace}`;
  }
  return url;
}

function addToPath(url, extension = '') {
  const [path, params] = url.split('?');
  let newUrl = `${path}${extension}`;

  if (params) {
    newUrl += `?${params}`;
  }

  return newUrl;
}
<commit_msg>fixup-adapter<commit_after>import { inject as service } from '@ember/service';
import Watchable from './watchable';

export default Watchable.extend({
  system: service(),

  findAll() {
    const namespace = this.get('system.activeNamespace');
    return this._super(...arguments).then(data => {
      data.forEach(job => {
        job.Namespace = namespace ? namespace.get('id') : 'default';
      });
      return data;
    });
  },

  findRecord(store, type, id, snapshot) {
    const [, namespace] = JSON.parse(id);
    const namespaceQuery = namespace && namespace !== 'default' ? { namespace } : {};

    return this._super(store, type, id, snapshot, namespaceQuery);
  },

  urlForFindAll() {
    const url = this._super(...arguments);
    const namespace = this.get('system.activeNamespace.id');
    return associateNamespace(url, namespace);
  },

  urlForFindRecord(id, type, hash) {
    const [name, namespace] = JSON.parse(id);
    let url = this._super(name, type, hash);
    return associateNamespace(url, namespace);
  },

  urlForUpdateRecord(id, type, hash) {
    const [name, namespace] = JSON.parse(id);
    let url = this._super(name, type, hash);
    return associateNamespace(url, namespace);
  },

  xhrKey(url, method, options = {}) {
    const plainKey = this._super(...arguments);
    const namespace = options.data && options.data.namespace;
    return associateNamespace(plainKey, namespace);
  },

  relationshipFallbackLinks: {
    summary: '/summary',
  },

  fetchRawDefinition(job) {
    const url = this.urlForFindRecord(job.get('id'), 'job');
    return this.ajax(url, 'GET');
  },

  forcePeriodic(job) {
    if (job.get('periodic')) {
      const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/periodic/force');
      return this.ajax(url, 'POST');
    }
  },

  stop(job) {
    const url = this.urlForFindRecord(job.get('id'), 'job');
    return this.ajax(url, 'DELETE');
  },

  parse(spec) {
    const url = addToPath(this.urlForFindAll('job'), '/parse');
    return this.ajax(url, 'POST', {
      data: {
        JobHCL: spec,
        Canonicalize: true,
      },
    });
  },

  plan(job) {
    const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/plan');
    return this.ajax(url, 'POST', {
      data: {
        Job: job.get('_newDefinitionJSON'),
        Diff: true,
      },
    }).then(json => {
      json.ID = job.get('id');
      this.get('store').pushPayload('job-plan', { jobPlans: [json] });
    });
  },

  // Running a job doesn't follow REST create semantics so it's easier to
  // treat it as an action.
  run(job) {
    return this.ajax(this.urlForCreateRecord('job'), 'POST', {
      data: {
        Job: job.get('_newDefinitionJSON'),
      },
    });
  },

  update(job) {
    return this.ajax(this.urlForUpdateRecord(job.get('id'), 'job'), 'POST', {
      data: {
        Job: job.get('_newDefinitionJSON'),
      },
    });
  },
});

function associateNamespace(url, namespace) {
  if (namespace && namespace !== 'default') {
    url += `?namespace=${namespace}`;
  }
  return url;
}

function addToPath(url, extension = '') {
  const [path, params] = url.split('?');
  let newUrl = `${path}${extension}`;

  if (params) {
    newUrl += `?${params}`;
  }

  return newUrl;
}
<|endoftext|>


<commit_before>var mapnik = require('mapnik');
var assert = require('assert');
var fs = require('fs');

describe('mapnik constants', function() {
    it('should have valid settings', function() {
        assert.ok(mapnik.settings);
        assert.ok(mapnik.settings.paths);
        assert.ok(mapnik.settings.paths.fonts.length);
        assert.ok(fs.statSync(mapnik.settings.paths.fonts));
        assert.ok(mapnik.settings.paths.input_plugins.length);
        // not true if statically linking plugins
        //assert.ok(fs.statSync(mapnik.settings.paths.input_plugins));

        // reloading the default plugins path should return false as no more plugins are registered
        assert.ok(!mapnik.register_datasources(mapnik.settings.paths.input_plugins));

        /* has version info */
        assert.ok(mapnik.versions);
        assert.ok(mapnik.versions.node);
        assert.ok(mapnik.versions.v8);
        assert.ok(mapnik.versions.mapnik);
        assert.ok(mapnik.versions.mapnik_number);
        assert.ok(mapnik.versions.boost);
        assert.ok(mapnik.versions.boost_number);

        assert.ok(mapnik.Geometry.Point, 1);
        assert.ok(mapnik.Geometry.LineString, 2);
        assert.ok(mapnik.Geometry.Polygon, 3);

        // make sure we have some
        assert.ok(mapnik.datasources().length > 0);
    });

    it('should have valid version info', function() {
        /* has version info */
        assert.ok(mapnik.versions);
        assert.ok(mapnik.versions.node);
        assert.ok(mapnik.versions.v8);
        assert.ok(mapnik.versions.mapnik);
        assert.ok(mapnik.versions.mapnik_number);
        assert.ok(mapnik.versions.boost);
        assert.ok(mapnik.versions.boost_number);

        assert.ok(mapnik.Geometry.Point, 1);
        assert.ok(mapnik.Geometry.LineString, 2);
        assert.ok(mapnik.Geometry.Polygon, 3);

        // make sure we have some
        assert.ok(mapnik.datasources().length > 0);
    });

    it('should expose Geometry enums', function() {
        assert.ok(mapnik.Geometry.Point, 1);
        assert.ok(mapnik.Geometry.LineString, 2);
        assert.ok(mapnik.Geometry.Polygon, 3);
    });
});
<commit_msg>reenable test of input plugin directory checking after https://github.com/mapnik/mapnik/commit/4057c2c822e6076dc0c33de2556f26fd0e772b57<commit_after>var mapnik = require('mapnik');
var assert = require('assert');
var fs = require('fs');

describe('mapnik constants', function() {
    it('should have valid settings', function() {
        assert.ok(mapnik.settings);
        assert.ok(mapnik.settings.paths);
        assert.ok(mapnik.settings.paths.fonts.length);
        assert.ok(fs.statSync(mapnik.settings.paths.fonts));
        assert.ok(mapnik.settings.paths.input_plugins.length);
        assert.ok(fs.statSync(mapnik.settings.paths.input_plugins));

        // reloading the default plugins path should return false as no more plugins are registered
        assert.ok(!mapnik.register_datasources(mapnik.settings.paths.input_plugins));

        /* has version info */
        assert.ok(mapnik.versions);
        assert.ok(mapnik.versions.node);
        assert.ok(mapnik.versions.v8);
        assert.ok(mapnik.versions.mapnik);
        assert.ok(mapnik.versions.mapnik_number);
        assert.ok(mapnik.versions.boost);
        assert.ok(mapnik.versions.boost_number);

        assert.ok(mapnik.Geometry.Point, 1);
        assert.ok(mapnik.Geometry.LineString, 2);
        assert.ok(mapnik.Geometry.Polygon, 3);

        // make sure we have some
        assert.ok(mapnik.datasources().length > 0);
    });

    it('should have valid version info', function() {
        /* has version info */
        assert.ok(mapnik.versions);
        assert.ok(mapnik.versions.node);
        assert.ok(mapnik.versions.v8);
        assert.ok(mapnik.versions.mapnik);
        assert.ok(mapnik.versions.mapnik_number);
        assert.ok(mapnik.versions.boost);
        assert.ok(mapnik.versions.boost_number);

        assert.ok(mapnik.Geometry.Point, 1);
        assert.ok(mapnik.Geometry.LineString, 2);
        assert.ok(mapnik.Geometry.Polygon, 3);

        // make sure we have some
        assert.ok(mapnik.datasources().length > 0);
    });

    it('should expose Geometry enums', function() {
        assert.ok(mapnik.Geometry.Point, 1);
        assert.ok(mapnik.Geometry.LineString, 2);
        assert.ok(mapnik.Geometry.Polygon, 3);
    });
});
<|endoftext|>


<commit_before>import { Morph, Text, show } from "./index.js"
import { pt, Color, Rectangle } from "lively.graphics";
import { arr, fun, obj } from "lively.lang";
import { signal } from "lively.bindings";

function asItem(obj) {
  return obj && obj.isListItem ? obj : {
    isListItem: true, value: obj, string: String(obj)
  }
}

class ListItemMorph extends Text {

  constructor(props) {
    super({
      fixedWidth: true, fixedHeight: false, readOnly: true, selectable: false,
      fill: null, textString: "", itemIndex: undefined, ...props
    });
  }

  displayItem(item, itemIndex, pos, isSelected = false) {
    this.itemIndex = itemIndex;
    this.textString = item.string || "no item.string";
    this.position = pos;
    this.width = this.owner.width;
    this.fill = isSelected ? Color.blue : null;
    this.fontColor = isSelected ? Color.white : Color.black;
  }

  onMouseDown(evt) {
    this.owner.owner.selectItemMorph(this);
  }
}

var listCommands = [
  {
    name: "page up",
    exec: (list) => {
      var index = list.selectedIndex,
          newIndex = Math.max(0, index - Math.round(list.height / list.itemHeight));
      list.gotoIndex(newIndex);
      return true;
    }
  },

  {
    name: "page down",
    exec: (list) => {
      var index = list.selectedIndex,
          newIndex = Math.min(list.items.length-1, index + Math.round(list.height / list.itemHeight))
      list.gotoIndex(newIndex);
      return true;
    }
  },

  {
    name: "goto first item",
    exec: (list) => { list.gotoIndex(0); return true; }
  },

  {
    name: "goto last item",
    exec: (list) => { list.gotoIndex(list.items.length-1); return true; }
  },

  {
    name: "arrow up",
    exec: (list) => { list.gotoIndex((list.selectedIndex || list.items.length) - 1); return true; }
  },

  {
    name: "arrow down",
    exec: (list) => {
      var index = list.selectedIndex,
          newIndex = ((typeof index === "number" ? index : -1) + 1) % list.items.length;
      list.gotoIndex(newIndex);
      return true;
    }
  }
];

export class List extends Morph {

  constructor(props) {
    super({
      layoutPolicy: "vertical",
      fontFamily: "Helvetica Neue, Arial, sans-serif",
      fontSize: 12,
      items: [],
      selectedIndexes: [],
      clipMode: "auto",
      ...props
    });
    this.update();
  }

  get connections() {
    return {selection: {signalOnAssignment: false}};
  }

  // horizonal or vertical (tiling?)
  get layoutPolicy() { return this.getProperty("layoutPolicy"); }
  set layoutPolicy(value) { this.addValueChange("layoutPolicy", value); }

  get fontFamily() { this.invalidateCache(); return this.getProperty("fontFamily"); }
  set fontFamily(value) { this.addValueChange("fontFamily", value); }

  get fontSize() { this.invalidateCache(); return this.getProperty("fontSize"); }
  set fontSize(value) { this.addValueChange("fontSize", value); }

  get items() { return this.getProperty("items"); }
  set items(items) {
    this.addValueChange("items", items.map(asItem));
    this.groupChangesWhile(undefined, () => this.update());
  }

  invalidateCache() {
    delete this._itemHeight;
  }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // items

  find(itemOrValue) {
    return this.items.find(item => item === itemOrValue || item.value === itemOrValue);
  }

  findIndex(itemOrValue) {
    return this.items.findIndex(item => item === itemOrValue || item.value === itemOrValue);
  }

  addItem(item) { return this.addItemAt(item); }

  addItemAt(item, index = this.items.length) {
    var items = this.items,
        index = Math.min(items.length, Math.max(0, index));
    items.splice(index, 0, asItem(item));

    this.addMethodCallChangeDoing({
      target: this,
      selector: "addItemAt",
      args: [item, index],
      undo: {
        target: this,
        selector: "removeItem",
        args: [item],
      }
    }, () => this.update());

  }

  removeItem(itemOrValue) {
    var item = this.find(itemOrValue),
        items = this.items,
        index = items.indexOf(item)
    if (index === -1) return;

    items.splice(index, 1);

    this.addMethodCallChangeDoing({
      target: this,
      selector: "removeItem",
      args: [item],
      undo: {
        target: this,
        selector: "addItemAt",
        args: [item, index],
      }
    }, () => this.update());

  }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // selection

  get selectedIndex() { return this.selectedIndexes[0]; }
  set selectedIndex(i) { return this.selectedIndexes = typeof i === "number" ? [i] : []; }
  get selectedIndexes() { return this.getProperty("selectedIndexes") || []; }
  set selectedIndexes(indexes) {
    var maxLength = this.items.length;
    this.addValueChange(
      "selectedIndexes",
      (indexes || []).filter(i => 0 <= i && i < maxLength));
    this.groupChangesWhile(undefined, () => this.update());
    signal(this, "selection", this.selection);
  }

  get selection() { return this.selections[0]; }
  set selection(itemOrValue) { this.selections = [itemOrValue]; }

  get selections() { return this.selectedIndexes.map(i => this.items[i].value); }
  set selections(sels) { this.selectedIndexes = sels.map(ea => this.findIndex(ea)); }

  get selectedItems() { return this.selectedIndexes.map(i => this.items[i]); }

  selectItemMorph(itemMorph) {
    this.selectedIndexes = [itemMorph.itemIndex];
  }

  gotoIndex(i) {
    this.scrollIndexIntoView(this.selectedIndex = i);
  }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // rendering

  get listItemContainer() {
    return this.getSubmorphNamed("listItemContainer") || this.addMorph({
      name: "listItemContainer", fill: null, clipMode: "visible"
    });
  }

  get itemMorphs() { return this.listItemContainer.submorphs; }

  get itemHeight() {
    return this._itemHeight
       || (this._itemHeight = this.env.fontMetric.sizeFor(this.fontFamily, this.fontSize, "X").height);
  }

  update() {
    var {
          itemHeight,
          items, itemMorphs, listItemContainer,
          selectedIndexes,
          scroll: {x: left, y: top},
          extent: {x: width, y: height},
          fontSize, fontFamily
        } = this,
        firstItemIndex = Math.floor(top / itemHeight),
        lastItemIndex = Math.ceil((top+height) / itemHeight);

    listItemContainer.extent = pt(this.width, itemHeight*items.length);

    for (var i = 0; i < lastItemIndex-firstItemIndex; i++) {
      var itemIndex = firstItemIndex+i,
          item = items[itemIndex];

      if (!item) {
        // if no items to display, remove remaining itemMorphs
        itemMorphs.slice(i).forEach(itemMorph => itemMorph.remove());
        break;
      }

      var itemMorph = itemMorphs[i] || (itemMorphs[i] = listItemContainer.addMorph(new ListItemMorph({fontFamily, fontSize})));

      itemMorph.displayItem(item, itemIndex,
        pt(0, itemHeight*(itemIndex)),
        selectedIndexes.includes(itemIndex));
    }

    itemMorphs.slice(lastItemIndex-firstItemIndex).forEach(ea => ea.remove());
  }

  scrollIndexIntoView(idx) {
    var {itemHeight, width, scroll} = this,
        itemBounds = new Rectangle(0, idx*itemHeight, width, itemHeight),
        visibleBounds = this.innerBounds().translatedBy(scroll),
        offsetX = 0, offsetY = 0
    if (itemBounds.bottom() > visibleBounds.bottom())
      offsetY = itemBounds.bottom() - visibleBounds.bottom()
    if (itemBounds.top() < visibleBounds.top())
      offsetY = itemBounds.top() - visibleBounds.top()
    this.scroll = scroll.addXY(offsetX, offsetY);
  }

  onScroll() {
    fun.debounceNamed(this.id + "scroll", 81, () => this.update(), true)();
  }

}

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

import { connect } from "lively.bindings";
import FontMetric from "./rendering/font-metric.js";

export class FilterableList extends Morph {

  constructor(props = {}) {
    super({borderWidth: 1, borderColor: Color.gray});

    var fontFamily = props.fontFamily || "Arial",
        fontSize = props.fontSize || 11,
        input = props.input || "",
        inputText = Text.makeInputLine({
          name: "input",
          textString: input,
          fill: null,
          borderWidth: 1, borderColor: Color.gray,
          padding: Rectangle.inset(2),
          fontSize, fontFamily
        }),
        list = new List({
          name: "list", items: [],
          fill: null,
          clipMode: "auto",
          fontSize, fontFamily
        })

    props = obj.dissoc(props, ["fontFamily", "fontSize", "input"]);

    this.submorphs = [inputText, list];
    this.state = {allItems: null}
    Object.assign(this, {
      items: [],
      extent: props.bounds ? props.bounds.extent() : pt(400, 360),
      ...props
    });

    this.relayout();

    connect(this.get("input"), "inputChanged", this, "updateFilter");

    this.addKeyBindings([
      {keys: "Down|Ctrl-N", command: "arrow down"},
      {keys: "Up|Ctrl-P", command: "arrow up"},
      {keys: "Ctrl-V|PageDown", command: "page down"},
      {keys: "Alt-V|PageUp", command: "page up"},
      {keys: "Alt-Shift-,", command: "goto first item"},
      {keys: "Alt-Shift-.", command: "goto last item"},
      {keys: "Enter", command: "accept input"},
      {keys: "Escape|Ctrl-G", command: "cancel"}
    ]);
  }

  onChange(change) {
    // if (change.prop === "extent") this.relayout();
    return super.onChange(change);
  }

  relayout() {
    var i = this.get("input"),
        l = this.get("list"),
        inputHeight = i.env.fontMetric.sizeFor(i.fontFamily, i.fontSize, "X").height + 2*2,
        ext = this.extent;
    i.setBounds(new Rectangle(0,0, this.width, inputHeight));
    l.setBounds(new Rectangle(0, inputHeight, ext.x, ext.y-inputHeight));
  }

  set items(items) {
    var l = this.get("list");
    l.items = items;
    this.state.allItems = l.items;
    this.updateFilter();
  }

  get selection() { return this.get("list").selection; }
  set selection(x) { this.get("list").selection = x; }
  get selectedIndex() { return this.get("list").selectedIndex; }
  set selectedIndex(x) { this.get("list").selectedIndex = x; }

  updateFilter() {
    var filterText = this.get("input").textString,
        filterTokens = filterText.split(/\s+/).map(ea => ea.toLowerCase()),
        filteredItems = this.state.allItems.filter(item => filterTokens.every(token => item.string.toLowerCase().includes(token)));
    this.get("list").items = filteredItems;
  }

  get commands()  {
    var list = this.get("list");
    return super.commands.concat([
      {
        name: "accept input",
        exec: (morph) => {
          signal(morph, "accepted", list.selection);
          return true;
        }
      },

      {
        name: "cancel",
        exec: (morph) => {
          signal(morph, "canceled");
          return true;
        }
      },

      {
        name: "page up",
        exec: (morph) => {
          var index = list.selectedIndex,
              newIndex = Math.max(0, index - Math.round(list.height / list.itemHeight));
          list.gotoIndex(newIndex);
          return true;
        }
      },

      {
        name: "page down",
        exec: (morph) => {
          var index = list.selectedIndex,
              newIndex = Math.min(list.items.length-1, index + Math.round(list.height / list.itemHeight))
          list.gotoIndex(newIndex);
          return true;
        }
      },

      {
        name: "goto first item",
        exec: (morph) => { list.gotoIndex(0); return true; }
      },

      {
        name: "goto last item",
        exec: (morph) => { list.gotoIndex(list.items.length-1); return true; }
      },

      {
        name: "arrow up",
        exec: (morph) => { list.gotoIndex((list.selectedIndex || list.items.length) - 1); return true; }
      },

      {
        name: "arrow down",
        exec: (morph) => {
          var index = list.selectedIndex,
              newIndex = ((typeof index === "number" ? index : -1) + 1) % list.items.length;
          list.gotoIndex(newIndex);
          return true;
        }
      }
    ])
  }

}
<commit_msg>list: safer list selection getter<commit_after>import { Morph, Text, show } from "./index.js"
import { pt, Color, Rectangle } from "lively.graphics";
import { arr, fun, obj } from "lively.lang";
import { signal } from "lively.bindings";

function asItem(obj) {
  return obj && obj.isListItem ? obj : {
    isListItem: true, value: obj, string: String(obj)
  }
}

class ListItemMorph extends Text {

  constructor(props) {
    super({
      fixedWidth: true, fixedHeight: false, readOnly: true, selectable: false,
      fill: null, textString: "", itemIndex: undefined, ...props
    });
  }

  displayItem(item, itemIndex, pos, isSelected = false) {
    this.itemIndex = itemIndex;
    this.textString = item.string || "no item.string";
    this.position = pos;
    this.width = this.owner.width;
    this.fill = isSelected ? Color.blue : null;
    this.fontColor = isSelected ? Color.white : Color.black;
  }

  onMouseDown(evt) {
    this.owner.owner.selectItemMorph(this);
  }
}

var listCommands = [
  {
    name: "page up",
    exec: (list) => {
      var index = list.selectedIndex,
          newIndex = Math.max(0, index - Math.round(list.height / list.itemHeight));
      list.gotoIndex(newIndex);
      return true;
    }
  },

  {
    name: "page down",
    exec: (list) => {
      var index = list.selectedIndex,
          newIndex = Math.min(list.items.length-1, index + Math.round(list.height / list.itemHeight))
      list.gotoIndex(newIndex);
      return true;
    }
  },

  {
    name: "goto first item",
    exec: (list) => { list.gotoIndex(0); return true; }
  },

  {
    name: "goto last item",
    exec: (list) => { list.gotoIndex(list.items.length-1); return true; }
  },

  {
    name: "arrow up",
    exec: (list) => { list.gotoIndex((list.selectedIndex || list.items.length) - 1); return true; }
  },

  {
    name: "arrow down",
    exec: (list) => {
      var index = list.selectedIndex,
          newIndex = ((typeof index === "number" ? index : -1) + 1) % list.items.length;
      list.gotoIndex(newIndex);
      return true;
    }
  }
];

export class List extends Morph {

  constructor(props) {
    super({
      layoutPolicy: "vertical",
      fontFamily: "Helvetica Neue, Arial, sans-serif",
      fontSize: 12,
      items: [],
      selectedIndexes: [],
      clipMode: "auto",
      ...props
    });
    this.update();
  }

  get connections() {
    return {selection: {signalOnAssignment: false}};
  }

  // horizonal or vertical (tiling?)
  get layoutPolicy() { return this.getProperty("layoutPolicy"); }
  set layoutPolicy(value) { this.addValueChange("layoutPolicy", value); }

  get fontFamily() { this.invalidateCache(); return this.getProperty("fontFamily"); }
  set fontFamily(value) { this.addValueChange("fontFamily", value); }

  get fontSize() { this.invalidateCache(); return this.getProperty("fontSize"); }
  set fontSize(value) { this.addValueChange("fontSize", value); }

  get items() { return this.getProperty("items"); }
  set items(items) {
    this.addValueChange("items", items.map(asItem));
    this.groupChangesWhile(undefined, () => this.update());
  }

  invalidateCache() {
    delete this._itemHeight;
  }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // items

  find(itemOrValue) {
    return this.items.find(item => item === itemOrValue || item.value === itemOrValue);
  }

  findIndex(itemOrValue) {
    return this.items.findIndex(item => item === itemOrValue || item.value === itemOrValue);
  }

  addItem(item) { return this.addItemAt(item); }

  addItemAt(item, index = this.items.length) {
    var items = this.items,
        index = Math.min(items.length, Math.max(0, index));
    items.splice(index, 0, asItem(item));

    this.addMethodCallChangeDoing({
      target: this,
      selector: "addItemAt",
      args: [item, index],
      undo: {
        target: this,
        selector: "removeItem",
        args: [item],
      }
    }, () => this.update());

  }

  removeItem(itemOrValue) {
    var item = this.find(itemOrValue),
        items = this.items,
        index = items.indexOf(item)
    if (index === -1) return;

    items.splice(index, 1);

    this.addMethodCallChangeDoing({
      target: this,
      selector: "removeItem",
      args: [item],
      undo: {
        target: this,
        selector: "addItemAt",
        args: [item, index],
      }
    }, () => this.update());

  }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // selection

  get selectedIndex() { return this.selectedIndexes[0]; }
  set selectedIndex(i) { return this.selectedIndexes = typeof i === "number" ? [i] : []; }
  get selectedIndexes() { return this.getProperty("selectedIndexes") || []; }
  set selectedIndexes(indexes) {
    var maxLength = this.items.length;
    this.addValueChange(
      "selectedIndexes",
      (indexes || []).filter(i => 0 <= i && i < maxLength));
    this.groupChangesWhile(undefined, () => this.update());
    signal(this, "selection", this.selection);
  }

  get selection() { return this.selections[0]; }
  set selection(itemOrValue) { this.selections = [itemOrValue]; }

  get selections() { return this.selectedIndexes.map(i => this.items[i] && this.items[i].value); }
  set selections(sels) { this.selectedIndexes = sels.map(ea => this.findIndex(ea)); }

  get selectedItems() { return this.selectedIndexes.map(i => this.items[i]); }

  selectItemMorph(itemMorph) {
    this.selectedIndexes = [itemMorph.itemIndex];
  }

  gotoIndex(i) {
    this.scrollIndexIntoView(this.selectedIndex = i);
  }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // rendering

  get listItemContainer() {
    return this.getSubmorphNamed("listItemContainer") || this.addMorph({
      name: "listItemContainer", fill: null, clipMode: "visible"
    });
  }

  get itemMorphs() { return this.listItemContainer.submorphs; }

  get itemHeight() {
    return this._itemHeight
       || (this._itemHeight = this.env.fontMetric.sizeFor(this.fontFamily, this.fontSize, "X").height);
  }

  update() {
    var {
          itemHeight,
          items, itemMorphs, listItemContainer,
          selectedIndexes,
          scroll: {x: left, y: top},
          extent: {x: width, y: height},
          fontSize, fontFamily
        } = this,
        firstItemIndex = Math.floor(top / itemHeight),
        lastItemIndex = Math.ceil((top+height) / itemHeight);

    listItemContainer.extent = pt(this.width, itemHeight*items.length);

    for (var i = 0; i < lastItemIndex-firstItemIndex; i++) {
      var itemIndex = firstItemIndex+i,
          item = items[itemIndex];

      if (!item) {
        // if no items to display, remove remaining itemMorphs
        itemMorphs.slice(i).forEach(itemMorph => itemMorph.remove());
        break;
      }

      var itemMorph = itemMorphs[i] || (itemMorphs[i] = listItemContainer.addMorph(new ListItemMorph({fontFamily, fontSize})));

      itemMorph.displayItem(item, itemIndex,
        pt(0, itemHeight*(itemIndex)),
        selectedIndexes.includes(itemIndex));
    }

    itemMorphs.slice(lastItemIndex-firstItemIndex).forEach(ea => ea.remove());
  }

  scrollIndexIntoView(idx) {
    var {itemHeight, width, scroll} = this,
        itemBounds = new Rectangle(0, idx*itemHeight, width, itemHeight),
        visibleBounds = this.innerBounds().translatedBy(scroll),
        offsetX = 0, offsetY = 0
    if (itemBounds.bottom() > visibleBounds.bottom())
      offsetY = itemBounds.bottom() - visibleBounds.bottom()
    if (itemBounds.top() < visibleBounds.top())
      offsetY = itemBounds.top() - visibleBounds.top()
    this.scroll = scroll.addXY(offsetX, offsetY);
  }

  onScroll() {
    fun.debounceNamed(this.id + "scroll", 81, () => this.update(), true)();
  }

}

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

import { connect } from "lively.bindings";
import FontMetric from "./rendering/font-metric.js";

export class FilterableList extends Morph {

  constructor(props = {}) {
    super({borderWidth: 1, borderColor: Color.gray});

    var fontFamily = props.fontFamily || "Arial",
        fontSize = props.fontSize || 11,
        input = props.input || "",
        inputText = Text.makeInputLine({
          name: "input",
          textString: input,
          fill: null,
          borderWidth: 1, borderColor: Color.gray,
          padding: Rectangle.inset(2),
          fontSize, fontFamily
        }),
        list = new List({
          name: "list", items: [],
          fill: null,
          clipMode: "auto",
          fontSize, fontFamily
        })

    props = obj.dissoc(props, ["fontFamily", "fontSize", "input"]);

    this.submorphs = [inputText, list];
    this.state = {allItems: null}
    Object.assign(this, {
      items: [],
      extent: props.bounds ? props.bounds.extent() : pt(400, 360),
      ...props
    });

    this.relayout();

    connect(this.get("input"), "inputChanged", this, "updateFilter");

    this.addKeyBindings([
      {keys: "Down|Ctrl-N", command: "arrow down"},
      {keys: "Up|Ctrl-P", command: "arrow up"},
      {keys: "Ctrl-V|PageDown", command: "page down"},
      {keys: "Alt-V|PageUp", command: "page up"},
      {keys: "Alt-Shift-,", command: "goto first item"},
      {keys: "Alt-Shift-.", command: "goto last item"},
      {keys: "Enter", command: "accept input"},
      {keys: "Escape|Ctrl-G", command: "cancel"}
    ]);
  }

  onChange(change) {
    // if (change.prop === "extent") this.relayout();
    return super.onChange(change);
  }

  relayout() {
    var i = this.get("input"),
        l = this.get("list"),
        inputHeight = i.env.fontMetric.sizeFor(i.fontFamily, i.fontSize, "X").height + 2*2,
        ext = this.extent;
    i.setBounds(new Rectangle(0,0, this.width, inputHeight));
    l.setBounds(new Rectangle(0, inputHeight, ext.x, ext.y-inputHeight));
  }

  set items(items) {
    var l = this.get("list");
    l.items = items;
    this.state.allItems = l.items;
    this.updateFilter();
  }

  get selection() { return this.get("list").selection; }
  set selection(x) { this.get("list").selection = x; }
  get selectedIndex() { return this.get("list").selectedIndex; }
  set selectedIndex(x) { this.get("list").selectedIndex = x; }

  updateFilter() {
    var filterText = this.get("input").textString,
        filterTokens = filterText.split(/\s+/).map(ea => ea.toLowerCase()),
        filteredItems = this.state.allItems.filter(item => filterTokens.every(token => item.string.toLowerCase().includes(token)));
    this.get("list").items = filteredItems;
  }

  get commands()  {
    var list = this.get("list");
    return super.commands.concat([
      {
        name: "accept input",
        exec: (morph) => {
          signal(morph, "accepted", list.selection);
          return true;
        }
      },

      {
        name: "cancel",
        exec: (morph) => {
          signal(morph, "canceled");
          return true;
        }
      },

      {
        name: "page up",
        exec: (morph) => {
          var index = list.selectedIndex,
              newIndex = Math.max(0, index - Math.round(list.height / list.itemHeight));
          list.gotoIndex(newIndex);
          return true;
        }
      },

      {
        name: "page down",
        exec: (morph) => {
          var index = list.selectedIndex,
              newIndex = Math.min(list.items.length-1, index + Math.round(list.height / list.itemHeight))
          list.gotoIndex(newIndex);
          return true;
        }
      },

      {
        name: "goto first item",
        exec: (morph) => { list.gotoIndex(0); return true; }
      },

      {
        name: "goto last item",
        exec: (morph) => { list.gotoIndex(list.items.length-1); return true; }
      },

      {
        name: "arrow up",
        exec: (morph) => { list.gotoIndex((list.selectedIndex || list.items.length) - 1); return true; }
      },

      {
        name: "arrow down",
        exec: (morph) => {
          var index = list.selectedIndex,
              newIndex = ((typeof index === "number" ? index : -1) + 1) % list.items.length;
          list.gotoIndex(newIndex);
          return true;
        }
      }
    ])
  }

}
<|endoftext|>


<commit_before>var assert = require('assert')
var crypto = require('crypto')
var fs = require('../../')
var path = require('path')
var testutil = require('testutil')
var mkdirp = fs.mkdirs
var testlib = require('../_lib/util')

/* global afterEach, beforeEach, describe, it */

var o777 = parseInt('777', 8)
var o666 = parseInt('666', 8)
var o444 = parseInt('444', 8)

var SIZE = 16 * 64 * 1024 + 7
var DIR = ''

describe('fs-extra', function () {
  beforeEach(function () {
    DIR = testutil.createTestDir('fs-extra')
  })

  afterEach(function (done) {
    fs.remove(DIR, done)
  })

  describe('+ copy()', function () {
    describe('> when the source is a file', function () {
      it('should copy the file asynchronously', function (done) {
        var fileSrc = path.join(DIR, 'TEST_fs-extra_src')
        var fileDest = path.join(DIR, 'TEST_fs-extra_copy')
        testlib.createFileWithData(fileSrc, SIZE)
        var srcMd5 = crypto.createHash('md5').update(fs.readFileSync(fileSrc)).digest('hex')
        var destMd5 = ''

        fs.copy(fileSrc, fileDest, function (err) {
          assert(!err)
          destMd5 = crypto.createHash('md5').update(fs.readFileSync(fileDest)).digest('hex')
          assert.strictEqual(srcMd5, destMd5)
          done()
        })
      })

      it('should return an error if the source file does not exist', function (done) {
        var fileSrc = 'we-simply-assume-this-file-does-not-exist.bin'
        var fileDest = path.join(DIR, 'TEST_fs-extra_copy')

        fs.copy(fileSrc, fileDest, function (err) {
          assert(err)
          done()
        })
      })

      it('should only copy files allowed by filter regex', function (done) {
        var srcFile1 = testlib.createFileWithData(path.join(DIR, '1.jade'), SIZE)
        var destFile1 = path.join(DIR, 'dest1.jade')
        var filter = /.html$|.css$/i
        fs.copy(srcFile1, destFile1, filter, function () {
          assert(!fs.existsSync(destFile1))
          done()
        })
      })

      it('should only copy files allowed by filter fn', function (done) {
        var srcFile1 = testlib.createFileWithData(path.join(DIR, '1.css'), SIZE)
        var destFile1 = path.join(DIR, 'dest1.css')
        var filter = function (s) { return s.split('.').pop() !== 'css'}
        fs.copy(srcFile1, destFile1, filter, function () {
          assert(!fs.existsSync(destFile1))
          done()
        })
      })

      it('accepts options object in place of filter', function (done) {
        var srcFile1 = testlib.createFileWithData(path.join(DIR, '1.jade'), SIZE)
        var destFile1 = path.join(DIR, 'dest1.jade')
        var options = {filter: /.html$|.css$/i}
        fs.copy(srcFile1, destFile1, options, function () {
          assert(!fs.existsSync(destFile1))
          done()
        })
      })

      describe('> when the destination dir does not exist', function () {
        it('should create the destination directory and copy the file', function (done) {
          var src = path.join(DIR, 'file.txt')
          var dest = path.join(DIR, 'this/path/does/not/exist/copied.txt')
          var data = 'did it copy?\n'

          fs.writeFileSync(src, data, 'utf8')

          fs.copy(src, dest, function (err) {
            var data2 = fs.readFileSync(dest, 'utf8')
            assert.strictEqual(data, data2)
            done(err)
          })
        })
      })
    })

    describe('> when the source is a directory', function () {
      describe('> when the source directory does not exist', function () {
        it('should return an error', function (done) {
          var ts = path.join(DIR, 'this_dir_does_not_exist')
          var td = path.join(DIR, 'this_dir_really_does_not_matter')
          fs.copy(ts, td, function (err) {
            assert(err)
            done()
          })
        })
      })

      it('should copy the directory asynchronously', function (done) {
        var FILES = 2
        var src = path.join(DIR, 'src')
        var dest = path.join(DIR, 'dest')

        mkdirp(src, function (err) {
          assert(!err)
          for (var i = 0; i < FILES; ++i) {
            testlib.createFileWithData(path.join(src, i.toString()), SIZE)
          }

          var subdir = path.join(src, 'subdir')
          mkdirp(subdir, function (err) {
            assert(!err)
            for (var i = 0; i < FILES; ++i) {
              testlib.createFileWithData(path.join(subdir, i.toString()), SIZE)
            }

            fs.copy(src, dest, function (err) {
              assert.ifError(err)
              assert(fs.existsSync(dest))

              for (var i = 0; i < FILES; ++i) {
                assert(fs.existsSync(path.join(dest, i.toString())))
              }

              var destSub = path.join(dest, 'subdir')
              for (var j = 0; j < FILES; ++j) {
                assert(fs.existsSync(path.join(destSub, j.toString())))
              }

              done()
            })
          })
        })
      })

      describe('> when the destination dir does not exist', function () {
        it('should create the destination directory and copy the file', function (done) {
          var src = path.join(DIR, 'data/')
          fs.mkdirsSync(src)
          var d1 = 'file1'
          var d2 = 'file2'

          fs.writeFileSync(path.join(src, 'f1.txt'), d1)
          fs.writeFileSync(path.join(src, 'f2.txt'), d2)

          var dest = path.join(DIR, 'this/path/does/not/exist/outputDir')

          fs.copy(src, dest, function (err) {
            var o1 = fs.readFileSync(path.join(dest, 'f1.txt'), 'utf8')
            var o2 = fs.readFileSync(path.join(dest, 'f2.txt'), 'utf8')

            assert.strictEqual(d1, o1)
            assert.strictEqual(d2, o2)

            done(err)
          })
        })
      })

      describe('> when src dir does not exist', function () {
        it('should return an error', function (done) {
          fs.copy('/does/not/exist', '/something/else', function (err) {
            assert(err instanceof Error)
            done()
          })
        })
      })
    })

    describe.skip('> REGRESSIONS', function () {
      // pretty UNIX specific, may not pass on windows... only test on Mac OS X 10.9
      it('should maintain file permissions and ownership', function (done) {
        var userid = require('userid')

        // http://man7.org/linux/man-pages/man2/stat.2.html
        var S_IFREG = parseInt('0100000', 8) // regular file
        var S_IFDIR = parseInt('0040000', 8) // directory

        var permDir = path.join(DIR, 'perms')
        fs.mkdirSync(permDir)

        var srcDir = path.join(permDir, 'src')
        fs.mkdirSync(srcDir)

        var f1 = path.join(srcDir, 'f1.txt')
        fs.writeFileSync(f1, '')
        fs.chmodSync(f1, o666)
        fs.chownSync(f1, process.getuid(), userid.gid('wheel'))
        var f1stats = fs.lstatSync(f1)
        assert.strictEqual(f1stats.mode - S_IFREG, o666)

        var d1 = path.join(srcDir, 'somedir')
        fs.mkdirSync(d1)
        fs.chmodSync(d1, o777)
        fs.chownSync(d1, process.getuid(), userid.gid('staff'))
        var d1stats = fs.lstatSync(d1)
        assert.strictEqual(d1stats.mode - S_IFDIR, o777)

        var f2 = path.join(d1, 'f2.bin')
        fs.writeFileSync(f2, '')
        fs.chmodSync(f2, o777)
        fs.chownSync(f2, process.getuid(), userid.gid('staff'))
        var f2stats = fs.lstatSync(f2)
        assert.strictEqual(f2stats.mode - S_IFREG, o777)

        var d2 = path.join(srcDir, 'crazydir')
        fs.mkdirSync(d2)
        fs.chmodSync(d2, o444)
        fs.chownSync(d2, process.getuid(), userid.gid('wheel'))
        var d2stats = fs.lstatSync(d2)
        assert.strictEqual(d2stats.mode - S_IFDIR, o444)

        var destDir = path.join(permDir, 'dest')
        fs.copy(srcDir, destDir, function (err) {
          assert.ifError(err)

          var newf1stats = fs.lstatSync(path.join(permDir, 'dest/f1.txt'))
          var newd1stats = fs.lstatSync(path.join(permDir, 'dest/somedir'))
          var newf2stats = fs.lstatSync(path.join(permDir, 'dest/somedir/f2.bin'))
          var newd2stats = fs.lstatSync(path.join(permDir, 'dest/crazydir'))

          assert.strictEqual(newf1stats.mode, f1stats.mode)
          assert.strictEqual(newd1stats.mode, d1stats.mode)
          assert.strictEqual(newf2stats.mode, f2stats.mode)
          assert.strictEqual(newd2stats.mode, d2stats.mode)

          done()
        })
      })
    })
  })
})
<commit_msg>test/copy/copy: removed usage of testutil<commit_after>var assert = require('assert')
var crypto = require('crypto')
var fs = require('fs')
var os = require('os')
var path = require('path')
var fse = require('../../')
var mkdirp = fse.mkdirs
var testlib = require('../_lib/util')

/* global afterEach, beforeEach, describe, it */

var o777 = parseInt('777', 8)
var o666 = parseInt('666', 8)
var o444 = parseInt('444', 8)

var SIZE = 16 * 64 * 1024 + 7
var TEST_DIR = ''

describe('fs-extra', function () {
  beforeEach(function (done) {
    TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy')
    fse.emptyDir(TEST_DIR, done)
  })

  afterEach(function (done) {
    fse.remove(TEST_DIR, done)
  })

  describe('+ copy()', function () {
    describe('> when the source is a file', function () {
      it('should copy the file asynchronously', function (done) {
        var fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
        var fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
        testlib.createFileWithData(fileSrc, SIZE)
        var srcMd5 = crypto.createHash('md5').update(fs.readFileSync(fileSrc)).digest('hex')
        var destMd5 = ''

        fse.copy(fileSrc, fileDest, function (err) {
          assert(!err)
          destMd5 = crypto.createHash('md5').update(fs.readFileSync(fileDest)).digest('hex')
          assert.strictEqual(srcMd5, destMd5)
          done()
        })
      })

      it('should return an error if the source file does not exist', function (done) {
        var fileSrc = 'we-simply-assume-this-file-does-not-exist.bin'
        var fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')

        fse.copy(fileSrc, fileDest, function (err) {
          assert(err)
          done()
        })
      })

      it('should only copy files allowed by filter regex', function (done) {
        var srcFile1 = testlib.createFileWithData(path.join(TEST_DIR, '1.jade'), SIZE)
        var destFile1 = path.join(TEST_DIR, 'dest1.jade')
        var filter = /.html$|.css$/i
        fse.copy(srcFile1, destFile1, filter, function () {
          assert(!fs.existsSync(destFile1))
          done()
        })
      })

      it('should only copy files allowed by filter fn', function (done) {
        var srcFile1 = testlib.createFileWithData(path.join(TEST_DIR, '1.css'), SIZE)
        var destFile1 = path.join(TEST_DIR, 'dest1.css')
        var filter = function (s) { return s.split('.').pop() !== 'css'}
        fse.copy(srcFile1, destFile1, filter, function () {
          assert(!fs.existsSync(destFile1))
          done()
        })
      })

      it('accepts options object in place of filter', function (done) {
        var srcFile1 = testlib.createFileWithData(path.join(TEST_DIR, '1.jade'), SIZE)
        var destFile1 = path.join(TEST_DIR, 'dest1.jade')
        var options = {filter: /.html$|.css$/i}
        fse.copy(srcFile1, destFile1, options, function () {
          assert(!fs.existsSync(destFile1))
          done()
        })
      })

      describe('> when the destination dir does not exist', function () {
        it('should create the destination directory and copy the file', function (done) {
          var src = path.join(TEST_DIR, 'file.txt')
          var dest = path.join(TEST_DIR, 'this/path/does/not/exist/copied.txt')
          var data = 'did it copy?\n'

          fs.writeFileSync(src, data, 'utf8')

          fse.copy(src, dest, function (err) {
            var data2 = fs.readFileSync(dest, 'utf8')
            assert.strictEqual(data, data2)
            done(err)
          })
        })
      })
    })

    describe('> when the source is a directory', function () {
      describe('> when the source directory does not exist', function () {
        it('should return an error', function (done) {
          var ts = path.join(TEST_DIR, 'this_dir_does_not_exist')
          var td = path.join(TEST_DIR, 'this_dir_really_does_not_matter')
          fse.copy(ts, td, function (err) {
            assert(err)
            done()
          })
        })
      })

      it('should copy the directory asynchronously', function (done) {
        var FILES = 2
        var src = path.join(TEST_DIR, 'src')
        var dest = path.join(TEST_DIR, 'dest')

        fse.mkdirs(src, function (err) {
          assert(!err)
          for (var i = 0; i < FILES; ++i) {
            testlib.createFileWithData(path.join(src, i.toString()), SIZE)
          }

          var subdir = path.join(src, 'subdir')
          mkdirp(subdir, function (err) {
            assert(!err)
            for (var i = 0; i < FILES; ++i) {
              testlib.createFileWithData(path.join(subdir, i.toString()), SIZE)
            }

            fse.copy(src, dest, function (err) {
              assert.ifError(err)
              assert(fs.existsSync(dest))

              for (var i = 0; i < FILES; ++i) {
                assert(fs.existsSync(path.join(dest, i.toString())))
              }

              var destSub = path.join(dest, 'subdir')
              for (var j = 0; j < FILES; ++j) {
                assert(fs.existsSync(path.join(destSub, j.toString())))
              }

              done()
            })
          })
        })
      })

      describe('> when the destination dir does not exist', function () {
        it('should create the destination directory and copy the file', function (done) {
          var src = path.join(TEST_DIR, 'data/')
          fse.mkdirsSync(src)
          var d1 = 'file1'
          var d2 = 'file2'

          fs.writeFileSync(path.join(src, 'f1.txt'), d1)
          fs.writeFileSync(path.join(src, 'f2.txt'), d2)

          var dest = path.join(TEST_DIR, 'this/path/does/not/exist/outputDir')

          fse.copy(src, dest, function (err) {
            var o1 = fs.readFileSync(path.join(dest, 'f1.txt'), 'utf8')
            var o2 = fs.readFileSync(path.join(dest, 'f2.txt'), 'utf8')

            assert.strictEqual(d1, o1)
            assert.strictEqual(d2, o2)

            done(err)
          })
        })
      })

      describe('> when src dir does not exist', function () {
        it('should return an error', function (done) {
          fse.copy('/does/not/exist', '/something/else', function (err) {
            assert(err instanceof Error)
            done()
          })
        })
      })
    })

    describe.skip('> REGRESSIONS', function () {
      // pretty UNIX specific, may not pass on windows... only test on Mac OS X 10.9
      it('should maintain file permissions and ownership', function (done) {
        var userid = require('userid')

        // http://man7.org/linux/man-pages/man2/stat.2.html
        var S_IFREG = parseInt('0100000', 8) // regular file
        var S_IFDIR = parseInt('0040000', 8) // directory

        var permDir = path.join(TEST_DIR, 'perms')
        fs.mkdirSync(permDir)

        var srcDir = path.join(permDir, 'src')
        fs.mkdirSync(srcDir)

        var f1 = path.join(srcDir, 'f1.txt')
        fs.writeFileSync(f1, '')
        fs.chmodSync(f1, o666)
        fs.chownSync(f1, process.getuid(), userid.gid('wheel'))
        var f1stats = fs.lstatSync(f1)
        assert.strictEqual(f1stats.mode - S_IFREG, o666)

        var d1 = path.join(srcDir, 'somedir')
        fs.mkdirSync(d1)
        fs.chmodSync(d1, o777)
        fs.chownSync(d1, process.getuid(), userid.gid('staff'))
        var d1stats = fs.lstatSync(d1)
        assert.strictEqual(d1stats.mode - S_IFDIR, o777)

        var f2 = path.join(d1, 'f2.bin')
        fs.writeFileSync(f2, '')
        fs.chmodSync(f2, o777)
        fs.chownSync(f2, process.getuid(), userid.gid('staff'))
        var f2stats = fs.lstatSync(f2)
        assert.strictEqual(f2stats.mode - S_IFREG, o777)

        var d2 = path.join(srcDir, 'crazydir')
        fs.mkdirSync(d2)
        fs.chmodSync(d2, o444)
        fs.chownSync(d2, process.getuid(), userid.gid('wheel'))
        var d2stats = fs.lstatSync(d2)
        assert.strictEqual(d2stats.mode - S_IFDIR, o444)

        var destDir = path.join(permDir, 'dest')
        fse.copy(srcDir, destDir, function (err) {
          assert.ifError(err)

          var newf1stats = fs.lstatSync(path.join(permDir, 'dest/f1.txt'))
          var newd1stats = fs.lstatSync(path.join(permDir, 'dest/somedir'))
          var newf2stats = fs.lstatSync(path.join(permDir, 'dest/somedir/f2.bin'))
          var newd2stats = fs.lstatSync(path.join(permDir, 'dest/crazydir'))

          assert.strictEqual(newf1stats.mode, f1stats.mode)
          assert.strictEqual(newd1stats.mode, d1stats.mode)
          assert.strictEqual(newf2stats.mode, f2stats.mode)
          assert.strictEqual(newd2stats.mode, d2stats.mode)

          done()
        })
      })
    })
  })
})
<|endoftext|>


<commit_before>describe('DeviceManager', function() {
  var chai = require('chai');
  var device_manager = require('../lib/device_manager.js');
  var DeviceManager = device_manager.DeviceManager;
  var EventEmitter = require('events').EventEmitter;
  var tlv = require('tlv');
  var TLV = tlv.TLV;
  chai.should();

  var mock_connector = require('./mock_connector');
  var mockDevice = new device_manager.Device('mock', 'mock');
  var subMockDevice = new device_manager.Device('mock', 'mock', null, mockDevice, 0);
  var secondMockDevice = new device_manager.Device('mock2', 'mock');

  describe('#DeviceManager', function() {
    it('should return an instance of DeviceManager', function() {
			var dm = new DeviceManager({});
      dm.should.be.instanceof(EventEmitter);
    });
  });

  describe('#createGetDeviceInfoCommand', function() {
    it('should return a DeviceCommand for GET DEVICE INFO', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var command = dm.createGetDeviceInfoCommand(mockDevice, cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.GET_DEVICE_INFO);
      command.parameters.length.should.equal(0);
      command.callback.should.equals(cb);
    });
  });

  describe('#createConfigureCommand', function() {
    it('should return a DeviceCommand for CONFIGURE', function() {
			var dm = new DeviceManager({});
      var param = new TLV(0x82, new Buffer([0xBA, 0xBA]));
      var cb = function() {};
      var command = dm.createConfigureCommand(mockDevice, [ param ], cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.CONFIGURE);
      command.parameters.length.should.equal(1);
      command.parameters[0].tag.should.equal(0xA0);
      command.parameters[0].getFirstChild(0x82).should.deep.equal(param);
      command.callback.should.equals(cb);
    });
  });

  describe('#createGetDataCommand', function() {
    it('should return a DeviceCommand for GET DATA with parameters', function() {
			var dm = new DeviceManager({});
      var param = new TLV(0x82, new Buffer([0xBA, 0xBA]));
      var cb = function() {};
      var command = dm.createGetDataCommand(mockDevice, [ param ], cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.GET_DATA);
      command.parameters.length.should.equal(1);
      command.parameters[0].tag.should.equal(0xA0);
      command.parameters[0].getFirstChild(0x82).should.deep.equal(param);
      command.callback.should.equals(cb);
    });

    it('should return a DeviceCommand for GET DATA without parameters', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var command = dm.createGetDataCommand(mockDevice, cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.GET_DATA);
      command.parameters.length.should.equal(0);
      command.callback.should.equals(cb);
    });
  });

  describe('#createPerformCommand', function() {
    it('should return a DeviceCommand for PERFORM with parameters', function() {
			var dm = new DeviceManager({});
      var param = new TLV(0x82, new Buffer([0xBA, 0xBA]));
      var cb = function() {};
      var command = dm.createPerformCommand(mockDevice, 0x90, [ param ], cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.PERFORM);
      command.parameters.length.should.equal(2);
      command.parameters[0].tag.should.equal(0x81);
      command.parameters[0].value.should.deep.equal(new Buffer([0x90]));
      command.parameters[1].tag.should.equal(0xA0);
      command.parameters[1].getFirstChild(0x82).should.deep.equal(param);
      command.callback.should.equals(cb);
    });

    it('should return a DeviceCommand for PERFORM without parameters', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var command = dm.createPerformCommand(mockDevice, 0x90, cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.PERFORM);
      command.parameters.length.should.equal(1)
      command.parameters[0].tag.should.equal(0x81);
      command.parameters[0].value.should.deep.equal(new Buffer([0x90]));
      command.callback.should.equals(cb);
    });
  });

  describe('#createSubscribeCommand', function() {
    it('should return a DeviceCommand for SUBSCRIBE', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var command = dm.createSubscribeCommand(mockDevice, [0xA2, 0xA1, 0xA4], cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.SUBSCRIBE);
      command.parameters.length.should.equal(1);
      command.parameters[0].tag.should.equal(0x81);
      command.parameters[0].value.should.deep.equal(new Buffer([0xA2, 0xA1, 0xA4]));
      command.callback.should.equals(cb);
    });
  });

  describe('#createUnsubscribeCommand', function() {
    it('should return a DeviceCommand for UNSUBSCRIBE', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var command = dm.createUnsubscribeCommand(mockDevice, [0xA2, 0xFF01, 0xA1, 0xA4], cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.UNSUBSCRIBE);
      command.parameters.length.should.equal(1);
      command.parameters[0].tag.should.equal(0x81);
      command.parameters[0].value.should.deep.equal(new Buffer([0xA2, 0xFF, 0x01, 0xA1, 0xA4]));
      command.callback.should.equals(cb);
    });
  });

  describe('#createPacket', function() {
    it('should create a TLV packet with all given commands', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var commands = [ dm.createGetDeviceInfoCommand(mockDevice, cb), dm.createGetDeviceInfoCommand(subMockDevice, cb), dm.createGetDataCommand(subMockDevice, [ new TLV(0x82, new Buffer([0xBA, 0xBA])) ], cb) ];
      var tlvPacket = dm.createPacket(commands);
      tlvPacket.tag.should.equal(0xE0);
      tlvPacket.value.length.should.equal(3);
      tlvPacket.value[0].tag.should.equal(0xA0);
      tlvPacket.value[0].value.length.should.equal(0);
      tlvPacket.value[1].tag.should.equal(0xA0);
      tlvPacket.value[1].value.length.should.equal(1);
      tlvPacket.value[1].value[0].tag.should.equal(0x80);
      tlvPacket.value[1].value[0].value.should.deep.equal(new Buffer([0x00]));
      tlvPacket.value[2].tag.should.equal(0xA2);
      tlvPacket.value[2].value.length.should.equal(2);
      tlvPacket.value[2].value[0].tag.should.equal(0x80);
      tlvPacket.value[2].value[0].value.should.deep.equal(new Buffer([0x00]));
      tlvPacket.value[2].value[1].tag.should.equal(0xA0);
    });
  });

  describe('#groupCommandsByPhysicalDevice', function() {
    it('should group commands by the physical device to which they should be sent', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var commands = [ dm.createGetDeviceInfoCommand(mockDevice, cb), dm.createGetDeviceInfoCommand(subMockDevice, cb), dm.createGetDeviceInfoCommand(secondMockDevice, cb) ];
      var sortedCommands = device_manager.groupCommandsByPhysicalDevice(commands);
      sortedCommands.get(mockDevice).length.should.equal(2);
      sortedCommands.get(secondMockDevice).length.should.equal(1);
    });
  });

  describe('#discoverDevices', function() {
    it('should discover devices on available connectors', function(done) {
			var dm = new DeviceManager({});
      dm._connectorLoader.loadedModules['mock'] = mock_connector;
      var deviceCount = 3;
      dm.discoverDevices(function(device) {
        deviceCount--;

        if (!deviceCount) {
          done();
        }
      });
    });
  });
});<commit_msg>deviceDiscovery test must be rewritten<commit_after>describe('DeviceManager', function() {
  var chai = require('chai');
  var device_manager = require('../lib/device_manager.js');
  var DeviceManager = device_manager.DeviceManager;
  var EventEmitter = require('events').EventEmitter;
  var tlv = require('tlv');
  var TLV = tlv.TLV;
  chai.should();

  var mock_connector = require('./mock_connector');
  var mockDevice = new device_manager.Device('mock', 'mock');
  var subMockDevice = new device_manager.Device('mock', 'mock', null, mockDevice, 0);
  var secondMockDevice = new device_manager.Device('mock2', 'mock');

  describe('#DeviceManager', function() {
    it('should return an instance of DeviceManager', function() {
			var dm = new DeviceManager({});
      dm.should.be.instanceof(EventEmitter);
    });
  });

  describe('#createGetDeviceInfoCommand', function() {
    it('should return a DeviceCommand for GET DEVICE INFO', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var command = dm.createGetDeviceInfoCommand(mockDevice, cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.GET_DEVICE_INFO);
      command.parameters.length.should.equal(0);
      command.callback.should.equals(cb);
    });
  });

  describe('#createConfigureCommand', function() {
    it('should return a DeviceCommand for CONFIGURE', function() {
			var dm = new DeviceManager({});
      var param = new TLV(0x82, new Buffer([0xBA, 0xBA]));
      var cb = function() {};
      var command = dm.createConfigureCommand(mockDevice, [ param ], cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.CONFIGURE);
      command.parameters.length.should.equal(1);
      command.parameters[0].tag.should.equal(0xA0);
      command.parameters[0].getFirstChild(0x82).should.deep.equal(param);
      command.callback.should.equals(cb);
    });
  });

  describe('#createGetDataCommand', function() {
    it('should return a DeviceCommand for GET DATA with parameters', function() {
			var dm = new DeviceManager({});
      var param = new TLV(0x82, new Buffer([0xBA, 0xBA]));
      var cb = function() {};
      var command = dm.createGetDataCommand(mockDevice, [ param ], cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.GET_DATA);
      command.parameters.length.should.equal(1);
      command.parameters[0].tag.should.equal(0xA0);
      command.parameters[0].getFirstChild(0x82).should.deep.equal(param);
      command.callback.should.equals(cb);
    });

    it('should return a DeviceCommand for GET DATA without parameters', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var command = dm.createGetDataCommand(mockDevice, cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.GET_DATA);
      command.parameters.length.should.equal(0);
      command.callback.should.equals(cb);
    });
  });

  describe('#createPerformCommand', function() {
    it('should return a DeviceCommand for PERFORM with parameters', function() {
			var dm = new DeviceManager({});
      var param = new TLV(0x82, new Buffer([0xBA, 0xBA]));
      var cb = function() {};
      var command = dm.createPerformCommand(mockDevice, 0x90, [ param ], cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.PERFORM);
      command.parameters.length.should.equal(2);
      command.parameters[0].tag.should.equal(0x81);
      command.parameters[0].value.should.deep.equal(new Buffer([0x90]));
      command.parameters[1].tag.should.equal(0xA0);
      command.parameters[1].getFirstChild(0x82).should.deep.equal(param);
      command.callback.should.equals(cb);
    });

    it('should return a DeviceCommand for PERFORM without parameters', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var command = dm.createPerformCommand(mockDevice, 0x90, cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.PERFORM);
      command.parameters.length.should.equal(1)
      command.parameters[0].tag.should.equal(0x81);
      command.parameters[0].value.should.deep.equal(new Buffer([0x90]));
      command.callback.should.equals(cb);
    });
  });

  describe('#createSubscribeCommand', function() {
    it('should return a DeviceCommand for SUBSCRIBE', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var command = dm.createSubscribeCommand(mockDevice, [0xA2, 0xA1, 0xA4], cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.SUBSCRIBE);
      command.parameters.length.should.equal(1);
      command.parameters[0].tag.should.equal(0x81);
      command.parameters[0].value.should.deep.equal(new Buffer([0xA2, 0xA1, 0xA4]));
      command.callback.should.equals(cb);
    });
  });

  describe('#createUnsubscribeCommand', function() {
    it('should return a DeviceCommand for UNSUBSCRIBE', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var command = dm.createUnsubscribeCommand(mockDevice, [0xA2, 0xFF01, 0xA1, 0xA4], cb);
      command.should.be.instanceof(device_manager.DeviceCommand);
      command.device.should.equal(mockDevice);
      command.type.should.equal(device_manager.CommandType.UNSUBSCRIBE);
      command.parameters.length.should.equal(1);
      command.parameters[0].tag.should.equal(0x81);
      command.parameters[0].value.should.deep.equal(new Buffer([0xA2, 0xFF, 0x01, 0xA1, 0xA4]));
      command.callback.should.equals(cb);
    });
  });

  describe('#createPacket', function() {
    it('should create a TLV packet with all given commands', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var commands = [ dm.createGetDeviceInfoCommand(mockDevice, cb), dm.createGetDeviceInfoCommand(subMockDevice, cb), dm.createGetDataCommand(subMockDevice, [ new TLV(0x82, new Buffer([0xBA, 0xBA])) ], cb) ];
      var tlvPacket = dm.createPacket(commands);
      tlvPacket.tag.should.equal(0xE0);
      tlvPacket.value.length.should.equal(3);
      tlvPacket.value[0].tag.should.equal(0xA0);
      tlvPacket.value[0].value.length.should.equal(0);
      tlvPacket.value[1].tag.should.equal(0xA0);
      tlvPacket.value[1].value.length.should.equal(1);
      tlvPacket.value[1].value[0].tag.should.equal(0x80);
      tlvPacket.value[1].value[0].value.should.deep.equal(new Buffer([0x00]));
      tlvPacket.value[2].tag.should.equal(0xA2);
      tlvPacket.value[2].value.length.should.equal(2);
      tlvPacket.value[2].value[0].tag.should.equal(0x80);
      tlvPacket.value[2].value[0].value.should.deep.equal(new Buffer([0x00]));
      tlvPacket.value[2].value[1].tag.should.equal(0xA0);
    });
  });

  describe('#groupCommandsByPhysicalDevice', function() {
    it('should group commands by the physical device to which they should be sent', function() {
			var dm = new DeviceManager({});
      var cb = function() {};
      var commands = [ dm.createGetDeviceInfoCommand(mockDevice, cb), dm.createGetDeviceInfoCommand(subMockDevice, cb), dm.createGetDeviceInfoCommand(secondMockDevice, cb) ];
      var sortedCommands = device_manager.groupCommandsByPhysicalDevice(commands);
      sortedCommands.get(mockDevice).length.should.equal(2);
      sortedCommands.get(secondMockDevice).length.should.equal(1);
    });
  });
});<|endoftext|>


<commit_before>'use strict';

UserConfig.registerOptions('jobs', {
  options: [
    {
      id: 'JustBuffTracker',
      name: {
        en: 'Only show the party buff tracker',
        de: 'Zeige nur den Gruppen Buff-Tracker',
        fr: 'Afficher seulement le tracker de buff de l\'équipe',
        cn: '仅显示团辅监控',
        ko: '파티 버프만 표시',
      },
      type: 'checkbox',
      default: false,
    },
    {
      id: 'LowerOpacityOutOfCombat',
      name: {
        en: 'Lower ui opacity when out of combat',
        de: 'Veringere die Deckkraft auserhalb des Kampfes',
        fr: 'Diminiuer l\'opacité de l\'UI si hors combat',
        cn: '非战斗状态时使UI透明化',
        ko: '전투 중이 아닐 때, UI 투명도 높이기',
      },
      type: 'checkbox',
      default: true,
    },
    {
      id: 'OpacityOutOfCombat',
      name: {
        en: 'Opacity of ui when out of combat',
        de: 'Deckkraft der UI auserhalb des Kampfes',
        fr: 'Opacité de l\'UI si hors combat',
        cn: '非战斗状态时UI透明度',
        ko: '전투 중이 아닐 때, UI 투명도',
      },
      type: 'float',
      default: 0.5,
    },
    {
      id: 'HideWellFedAboveSeconds',
      name: {
        en: 'Hide cheese icon when food > time (in seconds)',
        de: 'Verstecke das Käse Icon wenn Bufffood > Zeit (in Sekunden)',
        fr: 'Masquer l\'icône quand Repu > durée (en secondes)',
        cn: '食物提醒图标隐藏时间（秒）',
        ko: '남은 식사 효과 시간이 이 시간보다 길면, 치즈 아이콘 숨김 (단위: 초)',
      },
      type: 'integer',
      default: 15 * 60,
    },
    {
      id: 'ShowMPTickerOutOfCombat',
      name: {
        en: 'Show MP ticker out of combat',
        de: 'Zeige MP-Ticker auserhalb des Kampfes',
        fr: 'Afficher symbole MP si hors combat',
        cn: '非战斗状态时显示MP监控',
        ko: '전투 중이 아닐 때, MP 티커 표시',
      },
      type: 'checkbox',
      default: false,
    },
    {
      id: 'MidHealthThresholdPercent',
      name: {
        en: 'Percent of health considered middling',
        de: 'Prozent der Lebenspunkte (mittelmaß)',
        fr: 'Pourcentage de vie considéré comme moyenne',
        cn: '定义中等血量百分比',
        ko: '보통 HP로 취급될 HP비율 (1 = 100%)',
      },
      type: 'float',
      default: 0.8,
    },
    {
      id: 'LowHealthThresholdPercent',
      name: {
        en: 'Percent of health considered low',
        de: 'Prozent der Lebenspunkte (gering)',
        fr: 'Pourcentage de vie considéré comme bas',
        cn: '定义低血量百分比',
        ko: '낮은 HP로 취급될 HP비율 (1 = 100%)',
      },
      type: 'float',
      default: 0.2,
    },
    {
      id: 'BigBuffShowCooldownSeconds',
      name: {
        en: 'Minimum seconds on a cooldown before shown',
        de: 'Minimum an Sekunden für einen Cooldown vor der Anzeige',
        fr: 'Nombre minimal de secondes avant affichage du cooldown',
        cn: '显示团辅冷却倒计时最小时间',
        ko: '재사용 대기시간을 표시할 기준 시간(초 이하)',
      },
      type: 'float',
      default: 20,
    },
    {
      id: 'BigBuffIconWidth',
      name: {
        en: 'Width of buff icons (px)',
        de: 'Weite des Buff Icons (px)',
        fr: 'Largeur des icônes de buff (pixel)',
        cn: '团辅监控图标宽度（像素）',
        ko: '버프 아이콘 너비 (pixel)',
      },
      type: 'integer',
      default: 44,
    },
    {
      id: 'BigBuffIconHeight',
      name: {
        en: 'Height of buff icons (px)',
        de: 'Höhe des Buff Icons (px)',
        fr: 'Hauteur des icônes de buff (pixel)',
        cn: '团副监控图标高度（像素）',
        ko: '버프 아이콘 높이 (pixel)',
      },
      type: 'integer',
      default: 32,
    },
    {
      id: 'BigBuffBarHeight',
      name: {
        en: 'Height of buff timer bars (px)',
        de: 'Höhe der Buff-Timer Leisten (px)',
        fr: 'Hauteur des barres de temps des buffs (pixel)',
        cn: '团副监控计时条高度（像素）',
        ko: '버프 타이머 바 높이 (pixel)',
      },
      type: 'integer',
      default: 5,
    },
    {
      id: 'BigBuffTextHeight',
      name: {
        en: 'Height of buff text (px)',
        de: 'Höhe des Buff-Text (px)',
        fr: 'Hauteur des textes de buffs (pixel)',
        cn: '团副监控文字高度（像素）',
        ko: '버프 텍스트 높이 (pixel)',
      },
      type: 'integer',
      default: 0,
    },
    {
      id: 'BigBuffBorderSize',
      name: {
        en: 'Size of buff borders (px)',
        de: 'Größe der Buff-Ränder (px)',
        fr: 'Taille des bordures de buffs (pixel)',
        cn: '团副监控边框尺寸（像素）',
        ko: '버프 아이콘 테두리 크기 (pixel)',
      },
      type: 'integer',
      default: 1,
    },
    {
      id: 'GpAlarmPoint',
      name: {
        en: 'GP alarm threshold (0 to disable)',
        ko: 'GP 알람 설정값 (0 = 기능 정지)',
      },
      type: 'integer',
      default: 0,
    },
    {
      id: 'GpAlarmSoundVolume',
      name: {
        en: 'GP alarm sound (0-1)',
        ko: 'GP 알람 소리 크기 (0-1)',
      },
      type: 'float',
      default: 0.8,
    },
  ],
});
<commit_msg>jobs: CN translation for GP alarm config (#1355)<commit_after>'use strict';

UserConfig.registerOptions('jobs', {
  options: [
    {
      id: 'JustBuffTracker',
      name: {
        en: 'Only show the party buff tracker',
        de: 'Zeige nur den Gruppen Buff-Tracker',
        fr: 'Afficher seulement le tracker de buff de l\'équipe',
        cn: '仅显示团辅监控',
        ko: '파티 버프만 표시',
      },
      type: 'checkbox',
      default: false,
    },
    {
      id: 'LowerOpacityOutOfCombat',
      name: {
        en: 'Lower ui opacity when out of combat',
        de: 'Veringere die Deckkraft auserhalb des Kampfes',
        fr: 'Diminiuer l\'opacité de l\'UI si hors combat',
        cn: '非战斗状态时使UI透明化',
        ko: '전투 중이 아닐 때, UI 투명도 높이기',
      },
      type: 'checkbox',
      default: true,
    },
    {
      id: 'OpacityOutOfCombat',
      name: {
        en: 'Opacity of ui when out of combat',
        de: 'Deckkraft der UI auserhalb des Kampfes',
        fr: 'Opacité de l\'UI si hors combat',
        cn: '非战斗状态时UI透明度',
        ko: '전투 중이 아닐 때, UI 투명도',
      },
      type: 'float',
      default: 0.5,
    },
    {
      id: 'HideWellFedAboveSeconds',
      name: {
        en: 'Hide cheese icon when food > time (in seconds)',
        de: 'Verstecke das Käse Icon wenn Bufffood > Zeit (in Sekunden)',
        fr: 'Masquer l\'icône quand Repu > durée (en secondes)',
        cn: '食物提醒图标隐藏时间（秒）',
        ko: '남은 식사 효과 시간이 이 시간보다 길면, 치즈 아이콘 숨김 (단위: 초)',
      },
      type: 'integer',
      default: 15 * 60,
    },
    {
      id: 'ShowMPTickerOutOfCombat',
      name: {
        en: 'Show MP ticker out of combat',
        de: 'Zeige MP-Ticker auserhalb des Kampfes',
        fr: 'Afficher symbole MP si hors combat',
        cn: '非战斗状态时显示MP监控',
        ko: '전투 중이 아닐 때, MP 티커 표시',
      },
      type: 'checkbox',
      default: false,
    },
    {
      id: 'MidHealthThresholdPercent',
      name: {
        en: 'Percent of health considered middling',
        de: 'Prozent der Lebenspunkte (mittelmaß)',
        fr: 'Pourcentage de vie considéré comme moyenne',
        cn: '定义中等血量百分比',
        ko: '보통 HP로 취급될 HP비율 (1 = 100%)',
      },
      type: 'float',
      default: 0.8,
    },
    {
      id: 'LowHealthThresholdPercent',
      name: {
        en: 'Percent of health considered low',
        de: 'Prozent der Lebenspunkte (gering)',
        fr: 'Pourcentage de vie considéré comme bas',
        cn: '定义低血量百分比',
        ko: '낮은 HP로 취급될 HP비율 (1 = 100%)',
      },
      type: 'float',
      default: 0.2,
    },
    {
      id: 'BigBuffShowCooldownSeconds',
      name: {
        en: 'Minimum seconds on a cooldown before shown',
        de: 'Minimum an Sekunden für einen Cooldown vor der Anzeige',
        fr: 'Nombre minimal de secondes avant affichage du cooldown',
        cn: '显示团辅冷却倒计时最小时间',
        ko: '재사용 대기시간을 표시할 기준 시간(초 이하)',
      },
      type: 'float',
      default: 20,
    },
    {
      id: 'BigBuffIconWidth',
      name: {
        en: 'Width of buff icons (px)',
        de: 'Weite des Buff Icons (px)',
        fr: 'Largeur des icônes de buff (pixel)',
        cn: '团辅监控图标宽度（像素）',
        ko: '버프 아이콘 너비 (pixel)',
      },
      type: 'integer',
      default: 44,
    },
    {
      id: 'BigBuffIconHeight',
      name: {
        en: 'Height of buff icons (px)',
        de: 'Höhe des Buff Icons (px)',
        fr: 'Hauteur des icônes de buff (pixel)',
        cn: '团副监控图标高度（像素）',
        ko: '버프 아이콘 높이 (pixel)',
      },
      type: 'integer',
      default: 32,
    },
    {
      id: 'BigBuffBarHeight',
      name: {
        en: 'Height of buff timer bars (px)',
        de: 'Höhe der Buff-Timer Leisten (px)',
        fr: 'Hauteur des barres de temps des buffs (pixel)',
        cn: '团副监控计时条高度（像素）',
        ko: '버프 타이머 바 높이 (pixel)',
      },
      type: 'integer',
      default: 5,
    },
    {
      id: 'BigBuffTextHeight',
      name: {
        en: 'Height of buff text (px)',
        de: 'Höhe des Buff-Text (px)',
        fr: 'Hauteur des textes de buffs (pixel)',
        cn: '团副监控文字高度（像素）',
        ko: '버프 텍스트 높이 (pixel)',
      },
      type: 'integer',
      default: 0,
    },
    {
      id: 'BigBuffBorderSize',
      name: {
        en: 'Size of buff borders (px)',
        de: 'Größe der Buff-Ränder (px)',
        fr: 'Taille des bordures de buffs (pixel)',
        cn: '团副监控边框尺寸（像素）',
        ko: '버프 아이콘 테두리 크기 (pixel)',
      },
      type: 'integer',
      default: 1,
    },
    {
      id: 'GpAlarmPoint',
      name: {
        en: 'GP alarm threshold (0 to disable)',
        cn: '采集力警报阈值（0为禁用）',
        ko: 'GP 알람 설정값 (0 = 기능 정지)',
      },
      type: 'integer',
      default: 0,
    },
    {
      id: 'GpAlarmSoundVolume',
      name: {
        en: 'GP alarm sound (0-1)',
        cn: '采集力警报音量（0-1）',
        ko: 'GP 알람 소리 크기 (0-1)',
      },
      type: 'float',
      default: 0.8,
    },
  ],
});
<|endoftext|>