summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorqsm-odoo <qsm@odoo.com>2017-02-10 12:35:19 +0100
committerAaron Bohy <aab@odoo.com>2017-04-11 19:44:38 +0200
commit92f24f2cfee293c80a1c831dd6b8fb542476f22c (patch)
treec52fcae1abac4a1b43e5b203f10355b7dcc675bb
parent274a0aadc672be502b65ad693d5fe2a036ae06ab (diff)
[IMP] web: adapt field domain to new API
Add the implementation of the "FieldDomain" (widget='domain') in basic fields file.
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js224
-rw-r--r--addons/web/static/src/js/fields/field_registry.js3
-rw-r--r--addons/web/static/src/js/widgets/domain_selector.js6
-rw-r--r--addons/web/static/src/js/widgets/model_field_selector.js70
-rw-r--r--addons/web/static/src/xml/base.xml20
-rw-r--r--addons/web/static/test/fields/basic_fields_tests.js176
6 files changed, 443 insertions, 56 deletions
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 34c2b08e362..2dedec8978d 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -14,10 +14,14 @@ var core = require('web.core');
var crash_manager = require('web.crash_manager');
var datepicker = require('web.datepicker');
var dom = require('web.dom');
+var Domain = require('web.Domain');
+var DomainSelector = require('web.DomainSelector');
+var DomainSelectorDialog = require('web.DomainSelectorDialog');
var field_utils = require('web.field_utils');
var framework = require('web.framework');
var session = require('web.session');
var utils = require('web.utils');
+var view_dialogs = require('web.view_dialogs');
var qweb = core.qweb;
var _t = core._t;
@@ -1189,6 +1193,225 @@ var JournalDashboardGraph = AbstractField.extend({
});
/**
+ * The "Domain" field allows the user to construct a technical-prefix domain
+ * thanks to a tree-like interface and see the selected records in real time.
+ * In debug mode, an input is also there to be able to enter the prefix char
+ * domain directly (or to build advanced domains the tree-like interface does
+ * not allow to).
+ */
+var FieldDomain = AbstractField.extend({
+ /**
+ * The model this field widget works with could be dependant on another
+ * field value. This field widget must then be reset when a field is changed
+ * in the view.
+ */
+ resetOnAnyFieldChange: true,
+
+ events: _.extend({}, AbstractField.prototype.events, {
+ "click .o_domain_show_selection_button": "_onShowSelectionButtonClick",
+ "click .o_form_field_domain_dialog_button": "_onDialogEditButtonClick",
+ }),
+ custom_events: {
+ "domain_changed": "_onDomainSelectorValueChange",
+ "domain_selected": "_onDomainSelectorDialogValueChange",
+ },
+ /**
+ * @constructor
+ * @override init from AbstractField
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+
+ this.inDialog = !!this.nodeOptions.in_dialog;
+ this.modelOption = this.nodeOptions.model;
+ this.fsFilters = this.nodeOptions.fs_filters || {};
+
+ this.className = "o_form_field_domain";
+ if (this.mode === "edit") {
+ this.className += " o_edit_mode";
+ }
+ if (!this.inDialog) {
+ this.className += " o_inline_mode";
+ }
+
+ this._initModel();
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override isValid from AbstractField.isValid
+ * Parsing the char value is not enough for this field. It is considered
+ * valid if the internal domain selector was built correctly and that the
+ * query to the model to test the domain did not fail.
+ *
+ * @returns {boolean}
+ */
+ isValid: function () {
+ return (
+ this._super.apply(this, arguments)
+ && this._isValidForModel
+ );
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @override _render from AbstractField
+ * @returns {Deferred}
+ */
+ _render: function () {
+ // If there is no model, only change the non-domain-selector content
+ if (!this.domainModel) {
+ this._replaceContent();
+ return $.when();
+ }
+
+ // Convert char value to array value
+ var value = this.value || "[]";
+ var domain = Domain.prototype.stringToArray(value);
+
+ // Create the domain selector or change the value of the current one...
+ var def;
+ if (!this.domainSelector) {
+ this.domainSelector = new DomainSelector(this, this.domainModel, domain, {
+ readonly: this.mode === "readonly" || this.inDialog,
+ filters: this.fsFilters,
+ debugMode: session.debug,
+ });
+ def = this.domainSelector.prependTo(this.$el);
+ } else {
+ def = this.domainSelector.setDomain(domain);
+ }
+ // ... then query the model to get the number of matched record if valid
+ var self = this;
+ return def.then(function () {
+ var context = {}; // FIXME ?
+ return self.performModelRPC(self.domainModel, "search_count", [
+ domain,
+ context
+ ]).then(
+ function (data) {
+ self._isValidForModel = true;
+ return data;
+ },
+ function (error, e) {
+ e.preventDefault();
+ self._isValidForModel = false;
+ }
+ ).always(self._replaceContent.bind(self));
+ });
+ },
+ /**
+ * Render the field DOM except for the domain selector part. The full field
+ * DOM is composed of a DIV which contains the domain selector widget,
+ * followed by other content. This other content is handled by this method.
+ *
+ * @private
+ * @param {integer} [nbRecords=0] - The number of records the domain matches
+ */
+ _replaceContent(nbRecords) {
+ if (this._$content) {
+ this._$content.remove();
+ }
+ this._$content = $(qweb.render("FieldDomain.content", {
+ hasModel: !!this.domainModel,
+ isValid: !!this._isValidForModel,
+ nbRecords: nbRecords || 0,
+ inDialogEdit: this.inDialog && this.mode === "edit",
+ }));
+ this._$content.appendTo(this.$el);
+ },
+ /**
+ * @override _reset from AbstractField
+ * Check if the model the field works with has (to be) changed.
+ */
+ _reset: function () {
+ this._super.apply(this, arguments);
+ var oldDomainModel = this.domainModel;
+ this._initModel();
+ if (this.domainSelector && this.domainModel !== oldDomainModel) {
+ // If the model has changed, destroy the current domain selector
+ this.domainSelector.destroy();
+ this.domainSelector = null;
+ }
+ },
+ /**
+ * Checks the model the field must work with. It is either directly given
+ * in the field option or it is the value of another field in the view.
+ *
+ * @private
+ */
+ _initModel: function () {
+ this.domainModel = this.modelOption;
+ if (this.recordData.hasOwnProperty(this.domainModel)) {
+ this.domainModel = this.recordData[this.domainModel];
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called when the "Show selection" button is clicked
+ * -> Open a modal to see the matched records
+ *
+ * @param {Event} e
+ */
+ _onShowSelectionButtonClick: function (e) {
+ e.preventDefault();
+ new view_dialogs.SelectCreateDialog(this, {
+ title: _t("Selected records"),
+ res_model: this.domainModel,
+ domain: this.value || "[]",
+ no_create: true,
+ readonly: true,
+ disable_multiple_selection: true,
+ }).open();
+ },
+ /**
+ * Called when the "Edit domain" button is clicked (when using the in_dialog
+ * option) -> Open a DomainSelectorDialog to edit the value
+ *
+ * @param {Event} e
+ */
+ _onDialogEditButtonClick: function (e) {
+ e.preventDefault();
+ new DomainSelectorDialog(this, this.domainModel, this.value || "[]", {
+ readonly: this.mode === "readonly",
+ filters: this.fsFilters,
+ debugMode: session.debug,
+ }).open();
+ },
+ /**
+ * Called when the domain selector value is changed (do nothing if it is the
+ * one which is in a dialog (@see _onDomainSelectorDialogValueChange))
+ * -> Adapt the internal value state
+ *
+ * @param {OdooEvent} e
+ */
+ _onDomainSelectorValueChange: function (e) {
+ if (this.inDialog) return;
+ this._setValue(Domain.prototype.arrayToString(this.domainSelector.getDomain()));
+ },
+ /**
+ * Called when the in-dialog domain selector value is confirmed
+ * -> Adapt the internal value state
+ *
+ * @param {OdooEvent} e
+ */
+ _onDomainSelectorDialogValueChange: function (e) {
+ this._setValue(Domain.prototype.arrayToString(e.data.domain));
+ },
+});
+
+/**
* This widget is intended to be used on Text fields. It will provide Ace Editor
* for editing XML and Python.
*/
@@ -1275,6 +1498,7 @@ return {
FieldChar: FieldChar,
FieldDate: FieldDate,
FieldDateTime: FieldDateTime,
+ FieldDomain: FieldDomain,
FieldFloat: FieldFloat,
FieldFloatTime: FieldFloatTime,
FieldHtml: FieldHtml,
diff --git a/addons/web/static/src/js/fields/field_registry.js b/addons/web/static/src/js/fields/field_registry.js
index 0bfddd03098..405af71438b 100644
--- a/addons/web/static/src/js/fields/field_registry.js
+++ b/addons/web/static/src/js/fields/field_registry.js
@@ -24,6 +24,7 @@ registry
.add('boolean', basic_fields.FieldBoolean)
.add('date', basic_fields.FieldDate)
.add('datetime', basic_fields.FieldDateTime)
+ .add('domain', basic_fields.FieldDomain)
.add('text', basic_fields.FieldText)
.add('float', basic_fields.FieldFloat)
.add('char', basic_fields.FieldChar)
@@ -63,4 +64,4 @@ registry
.add('statusbar', relational_fields.FieldStatus)
.add('one2many_list', relational_fields.FieldOne2Many);
-}); \ No newline at end of file
+});
diff --git a/addons/web/static/src/js/widgets/domain_selector.js b/addons/web/static/src/js/widgets/domain_selector.js
index f7c67aa37cb..ba1ac9e77c9 100644
--- a/addons/web/static/src/js/widgets/domain_selector.js
+++ b/addons/web/static/src/js/widgets/domain_selector.js
@@ -301,6 +301,12 @@ var DomainSelector = DomainTree.extend({
}
return this._super(domain);
},
+ setDomain: function (domain) {
+ if (Domain.prototype.arrayToString(domain) === Domain.prototype.arrayToString(this.getDomain())) {
+ return $.when();
+ }
+ return this._redraw(domain);
+ },
_postRender: function () {
this._super.apply(this, arguments);
diff --git a/addons/web/static/src/js/widgets/model_field_selector.js b/addons/web/static/src/js/widgets/model_field_selector.js
index 907368920ac..9c843937c90 100644
--- a/addons/web/static/src/js/widgets/model_field_selector.js
+++ b/addons/web/static/src/js/widgets/model_field_selector.js
@@ -2,11 +2,20 @@ odoo.define("web.ModelFieldSelector", function (require) {
"use strict";
var core = require("web.core");
-var Model = require("web.DataModel");
var Widget = require("web.Widget");
var _t = core._t;
+/**
+ * Field Selector Cache - TODO Should be improved to use external cache ?
+ * - Stores fields per model used in field selector
+ * @see ModelFieldSelector._getModelFieldsFromCache
+ */
+var modelFieldsCache = {
+ cache: {},
+ cacheDefs: {},
+};
+
/// The ModelFieldSelector widget can be used to select a particular field chain from a given model.
var ModelFieldSelector = Widget.extend({
template: "FieldSelector",
@@ -32,7 +41,7 @@ var ModelFieldSelector = Widget.extend({
},
// Handle a direct change in the debug input
- "change input": function() {
+ "change input": function () {
var userChain = this.$input.val();
if (!this.options.followRelations) {
var fields = userChain.split(".");
@@ -208,7 +217,7 @@ var ModelFieldSelector = Widget.extend({
/// @return a deferred which is resolved once the last page is shown
_prefill: function () {
this.pages = [];
- return this._pushPageData(this.model).then((function() {
+ return this._pushPageData(this.model).then((function () {
return (this.chain ? processChain.call(this, this.chain.split(".").reverse()) : $.when());
}).bind(this));
@@ -234,7 +243,7 @@ var ModelFieldSelector = Widget.extend({
if (this.model === model && this.options.fields) {
def = $.when(sortFields(this.options.fields));
} else {
- def = fieldsCache.getFields(model, this.options.filters);
+ def = this._getModelFieldsFromCache(model, this.options.filters);
}
return def.then((function (fields) {
this.pages.push(fields);
@@ -294,35 +303,34 @@ var ModelFieldSelector = Widget.extend({
name: name,
});
},
-});
-
-/// Field Selector Cache
-///
-/// * Stores fields per model used in field selector
-/// * Apply filters on the fly
-var fieldsCache = {
- cache: {},
- cacheDefs: {},
- getFields: function (model, filters) {
- return (this.cacheDefs[model] ? this.cacheDefs[model] : this.updateCache(model)).then((function () {
- return this.filter(model, filters);
- }).bind(this));
- },
- updateCache: function (model) {
- this.cacheDefs[model] = new Model(model).call("fields_get", [
- false,
- ["store", "searchable", "type", "string", "relation", "selection", "related"],
- ]).then((function (fields) {
- this.cache[model] = sortFields(fields);
+ /**
+ * The _getFieldsFromCache method searches the cache for the given model
+ * fields, according to the given filter. If the cache does not know about
+ * the model, the cache is updated.
+ *
+ * @param {string} model
+ * @param {Object} filters @see ModelFieldSelector.init.options.filters
+ *
+ * @returns {Object[]} a list of the model fields info, sorted by field
+ * non-technical name
+ */
+ _getModelFieldsFromCache: function (model, filters) {
+ var def = modelFieldsCache.cacheDefs[model];
+ if (!def) {
+ def = modelFieldsCache.cacheDefs[model] = this.performModelRPC(model, "fields_get", [
+ false,
+ ["store", "searchable", "type", "string", "relation", "selection", "related"],
+ ]).then((function (fields) {
+ modelFieldsCache.cache[model] = sortFields(fields);
+ }).bind(this));
+ }
+ return def.then((function () {
+ return _.filter(modelFieldsCache.cache[model], function (f) {
+ return !filters.searchable || f.searchable;
+ });
}).bind(this));
- return this.cacheDefs[model];
},
- filter: function (model, filters) {
- return _.filter(this.cache[model], function (f) {
- return !filters.searchable || f.searchable;
- });
- },
-};
+});
function sortFields(fields) {
return _.chain(fields)
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index f5fb05044e6..6d51198fd8b 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -665,19 +665,19 @@
t-att-maxlength="widget.field.size"/>
</t>
-<div t-name="FieldDomain" t-attf-class="o_form_field_domain#{widget.get('effective_readonly') ? '' : ' o_edit_mode'}#{widget.options.in_dialog ? '' : ' o_inline_mode'}">
- <!-- domain selector will be instantiated here -->
- <div class="o_form_field_domain_panel">
+<t t-name="FieldDomain.content">
+ <div t-if="hasModel" class="o_form_field_domain_panel">
<i class="fa fa-arrow-right"/>
- <button class="btn btn-xs btn-default o_domain_show_selection_button" type="button">
- <span class="o_domain_records_count"/> record(s)
+
+ <button t-if="isValid" class="btn btn-xs btn-default o_domain_show_selection_button" type="button">
+ <t t-esc="nbRecords"/> record(s)
</button>
- <span class="text-warning o_domain_error_message hidden"><i class="fa fa-exclamation-triangle"/> Invalid domain</span>
- <button t-if="widget.options.in_dialog &amp;&amp; !widget.get('effective_readonly')"
- class="btn btn-xs btn-primary o_form_field_domain_dialog_button">Edit domain</button>
+ <span t-else="" class="text-warning"><i class="fa fa-exclamation-triangle"/> Invalid domain</span>
+
+ <button t-if="inDialogEdit" class="btn btn-xs btn-primary o_form_field_domain_dialog_button">Edit domain</button>
</div>
- <div class="o_domain_model_missing o_hidden">Select a model to add a filter.</div>
-</div>
+ <div t-else="">Select a model to add a filter.</div>
+</t>
<t t-name="DomainNode.ControlPanel">
<div t-if="!widget.readonly &amp;&amp; !widget.noControlPanel" class="btn-group btn-group-sm pull-right o_domain_node_control_panel">
diff --git a/addons/web/static/test/fields/basic_fields_tests.js b/addons/web/static/test/fields/basic_fields_tests.js
index e1dbb1f0b24..078008ced35 100644
--- a/addons/web/static/test/fields/basic_fields_tests.js
+++ b/addons/web/static/test/fields/basic_fields_tests.js
@@ -16,17 +16,17 @@ QUnit.module('basic_fields', {
this.data = {
partner: {
fields: {
- date: {string: "A date", type: "date"},
- display_name: { string: "Displayed name", type: "char" },
- foo: {string: "Foo", type: "char", default: "My little Foo Value"},
- bar: {string: "Bar", type: "boolean", default: true},
- int_field: {string: "int_field", type: "integer", sortable: true},
- qux: {string: "Qux", type: "float", digits: [16,1] },
- p: {string: "one2many field", type: "one2many", relation: 'partner'},
- trululu: {string: "Trululu", type: "many2one", relation: 'partner'},
- timmy: { string: "pokemon", type: "many2many", relation: 'partner_type'},
- product_id: {string: "Product", type: "many2one", relation: 'product'},
- sequence: {type: "integer", string: "Sequence"},
+ date: {string: "A date", type: "date", searchable: true},
+ display_name: {string: "Displayed name", type: "char", searchable: true},
+ foo: {string: "Foo", type: "char", default: "My little Foo Value", searchable: true},
+ bar: {string: "Bar", type: "boolean", default: true, searchable: true},
+ int_field: {string: "int_field", type: "integer", sortable: true, searchable: true},
+ qux: {string: "Qux", type: "float", digits: [16,1], searchable: true},
+ p: {string: "one2many field", type: "one2many", relation: 'partner', searchable: true},
+ trululu: {string: "Trululu", type: "many2one", relation: 'partner', searchable: true},
+ timmy: {string: "pokemon", type: "many2many", relation: 'partner_type', searchable: true},
+ product_id: {string: "Product", type: "many2one", relation: 'product', searchable: true},
+ sequence: {type: "integer", string: "Sequence", searchable: true},
},
records: [{
id: 1,
@@ -62,7 +62,7 @@ QUnit.module('basic_fields', {
},
product: {
fields: {
- name: {string: "Product Name", type: "char"}
+ name: {string: "Product Name", type: "char", searchable: true}
},
records: [{
id: 37,
@@ -74,8 +74,8 @@ QUnit.module('basic_fields', {
},
partner_type: {
fields: {
- name: {string: "Partner Type", type: "char"},
- color: {string: "Color index", type: "int"},
+ name: {string: "Partner Type", type: "char", searchable: true},
+ color: {string: "Color index", type: "integer", searchable: true},
},
records: [
{id: 12, display_name: "gold", color: 2},
@@ -539,6 +539,154 @@ QUnit.module('basic_fields', {
"date input should have the focus (make sure the test window had the focus)");
});
+ QUnit.module('FieldDomain');
+
+ QUnit.test('basic domain field usage is ok', function (assert) {
+ assert.expect(6);
+
+ this.data.partner.records[0].foo = "[]";
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form>' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="foo" widget="domain" options="{\'model\': \'partner_type\'}"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ // As the domain is empty, there should be a button to add the first
+ // domain part
+ var $domain = form.$(".o_form_field_domain");
+ var $domainAddFirstNodeButton = $domain.find(".o_domain_add_first_node_button");
+ assert.equal($domainAddFirstNodeButton.length, 1,
+ "there should be a button to create first domain element");
+
+ // Clicking on the button should add the [["id", "=", "1"]] domain, so
+ // there should be a field selector in the DOM
+ $domainAddFirstNodeButton.click();
+ var $fieldSelector = $domain.find(".o_field_selector");
+ assert.equal($fieldSelector.length, 1,
+ "there should be a field selector");
+
+ // Focusing the field selector input should open the field selector
+ // popover
+ $fieldSelector.find("> input").focus();
+ var $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover");
+ assert.ok($fieldSelectorPopover.is(":visible"),
+ "field selector popover should be visible");
+
+ // The popover should contain the list of partner_type fields and so
+ // there should be the "Color index" field
+ var $lis = $fieldSelectorPopover.find("li");
+ var $colorIndex = $();
+ $lis.each(function () {
+ var $li = $(this);
+ if ($li.html().indexOf("Color index") >= 0) {
+ $colorIndex = $li;
+ }
+ });
+ assert.equal($colorIndex.length, 1,
+ "field selector popover should contain 'Color index' field");
+
+ // Clicking on this field should close the popover, then changing the
+ // associated value should reveal one matched record
+ $colorIndex.click();
+ $domain.find(".o_domain_leaf_value_input").val(2).change();
+ assert.equal($domain.find(".o_domain_show_selection_button").text().trim().substr(0, 2), "1 ",
+ "changing color value to 2 should reveal only one record");
+
+ // Saving the form view should show a readonly domain containing the
+ // "color" field
+ form.$buttons.find('.o_form_button_save').click();
+ $domain = form.$(".o_form_field_domain");
+ assert.ok($domain.html().indexOf("color") >= 0,
+ "field selector readonly value should now contain 'color'");
+ });
+
+ QUnit.test('domain field is correctly reset on every view change', function (assert) {
+ assert.expect(7);
+
+ this.data.partner.records[0].foo = '[["id","=",1]]';
+ this.data.partner.fields.bar.type = "char";
+ this.data.partner.records[0].bar = "product";
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form>' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="bar"/>' +
+ '<field name="foo" widget="domain" options="{\'model\': \'bar\'}"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ // As the domain is equal to [["id", "=", 1]] there should be a field
+ // selector to change this
+ var $domain = form.$(".o_form_field_domain");
+ var $fieldSelector = $domain.find(".o_field_selector");
+ assert.equal($fieldSelector.length, 1,
+ "there should be a field selector");
+
+ // Focusing its input should open the field selector popover
+ $fieldSelector.find("> input").focus();
+ var $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover");
+ assert.ok($fieldSelectorPopover.is(":visible"),
+ "field selector popover should be visible");
+
+ // As the value of the "bar" field is "product", the field selector
+ // popover should contain the list of "product" fields
+ var $lis = $fieldSelectorPopover.find("li");
+ var $sampleLi = $();
+ $lis.each(function () {
+ var $li = $(this);
+ if ($li.html().indexOf("Product Name") >= 0) {
+ $sampleLi = $li;
+ }
+ });
+ assert.strictEqual($lis.length, 1,
+ "field selector popover should contain only one field");
+ assert.strictEqual($sampleLi.length, 1,
+ "field selector popover should contain 'Product Name' field");
+
+ // Now change the value of the "bar" field to "partner_type"
+ form.$(".o_field_widget.o_form_input").click().val("partner_type").trigger("input");
+
+ // Refocusing the field selector input should open the popover again
+ $fieldSelector = form.$(".o_field_selector");
+ $fieldSelector.find("> input").focus();
+ $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover");
+ assert.ok($fieldSelectorPopover.is(":visible"),
+ "field selector popover should be visible");
+
+ // Now the list of fields should be the ones of the "partner_type" model
+ $lis = $fieldSelectorPopover.find("li");
+ $sampleLi = $();
+ $lis.each(function () {
+ var $li = $(this);
+ if ($li.html().indexOf("Color index") >= 0) {
+ $sampleLi = $li;
+ }
+ });
+ assert.strictEqual($lis.length, 2,
+ "field selector popover should contain two fields");
+ assert.strictEqual($sampleLi.length, 1,
+ "field selector popover should contain 'Color index' field");
+ });
});
});
});