odoo.define('web.field_many_to_many_tests', function (require) {
"use strict";

var FormView = require('web.FormView');
var testUtils = require('web.test_utils');
var createView = testUtils.createView;

QUnit.module('fields', {}, function () {

    QUnit.module('relational_fields', {
        beforeEach: function () {
            this.data = {
                partner: {
                    fields: {
                        display_name: { string: "Displayed name", type: "char" },
                        foo: { string: "Foo", type: "char", default: "My little Foo Value" },
                        int_field: { string: "int_field", type: "integer", sortable: true },
                        turtles: { string: "one2many turtle field", type: "one2many", relation: 'turtle', relation_field: 'turtle_trululu' },
                        timmy: { string: "pokemon", type: "many2many", relation: 'partner_type' },
                        color: {
                            type: "selection",
                            selection: [['red', "Red"], ['black', "Black"]],
                            default: 'red',
                            string: "Color",
                        },
                        user_id: { string: "User", type: 'many2one', relation: 'user' },
                        reference: {
                            string: "Reference Field", type: 'reference', selection: [
                                ["product", "Product"], ["partner_type", "Partner Type"], ["partner", "Partner"]]
                        },
                    },
                    records: [{
                        id: 1,
                        display_name: "first record",
                        foo: "yop",
                        int_field: 10,
                        turtles: [2],
                        timmy: [],
                        user_id: 17,
                        reference: 'product,37',
                    }, {
                        id: 2,
                        display_name: "second record",
                        foo: "blip",
                        int_field: 9,
                        timmy: [],
                        user_id: 17,
                    }, {
                        id: 4,
                        display_name: "aaa",
                    }],
                    onchanges: {},
                },
                product: {
                    fields: {
                        name: { string: "Product Name", type: "char" }
                    },
                    records: [{
                        id: 37,
                        display_name: "xphone",
                    }, {
                        id: 41,
                        display_name: "xpad",
                    }]
                },
                partner_type: {
                    fields: {
                        name: { string: "Partner Type", type: "char" },
                        color: { string: "Color index", type: "integer" },
                    },
                    records: [
                        { id: 12, display_name: "gold", color: 2 },
                        { id: 14, display_name: "silver", color: 5 },
                    ]
                },
                turtle: {
                    fields: {
                        display_name: { string: "Displayed name", type: "char" },
                        turtle_foo: { string: "Foo", type: "char" },
                        turtle_bar: { string: "Bar", type: "boolean", default: true },
                        partner_ids: { string: "Partner", type: "many2many", relation: 'partner' },
                    },
                    records: [{
                        id: 1,
                        display_name: "leonardo",
                        turtle_foo: "yop",
                        partner_ids: [],
                    }, {
                        id: 2,
                        display_name: "donatello",
                        turtle_foo: "blip",
                        partner_ids: [2, 4],
                    }, {
                        id: 3,
                        display_name: "raphael",
                        turtle_foo: "kawa",
                        partner_ids: [],
                    }],
                    onchanges: {},
                },
                user: {
                    fields: {
                        name: { string: "Name", type: "char" },
                    },
                    records: [{
                        id: 17,
                        name: "Aline",
                    }, {
                        id: 19,
                        name: "Christine",
                    }]
                },
            };
        }
    }, function () {
        QUnit.module('FieldMany2Many');

        QUnit.test('many2many kanban: edition', function (assert) {
            assert.expect(32);

            this.data.partner.records[0].timmy = [12, 14];
            this.data.partner_type.records.push({ id: 15, display_name: "red", color: 6 });
            this.data.partner_type.records.push({ id: 18, display_name: "yellow", color: 4 });
            this.data.partner_type.records.push({ id: 21, display_name: "blue", color: 1 });

            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="timmy">' +
                    '<kanban>' +
                    '<field name="display_name"/>' +
                    '<templates>' +
                    '<t t-name="kanban-box">' +
                    '<div class="oe_kanban_global_click">' +
                    '<a t-if="!read_only_mode" type="delete" class="fa fa-times float-right delete_icon"/>' +
                    '<span><t t-esc="record.display_name.value"/></span>' +
                    '</div>' +
                    '</t>' +
                    '</templates>' +
                    '</kanban>' +
                    '<form string="Partners">' +
                    '<field name="display_name"/>' +
                    '</form>' +
                    '</field>' +
                    '</form>',
                archs: {
                    'partner_type,false,form': '<form string="Types"><field name="display_name"/></form>',
                    'partner_type,false,list': '<tree string="Types"><field name="display_name"/></tree>',
                    'partner_type,false,search': '<search string="Types">' +
                        '<field name="name" string="Name"/>' +
                        '</search>',
                },
                res_id: 1,
                mockRPC: function (route, args) {
                    if (route === '/web/dataset/call_kw/partner_type/write') {
                        assert.strictEqual(args.args[1].display_name, "new name", "should write 'new_name'");
                    }
                    if (route === '/web/dataset/call_kw/partner_type/create') {
                        assert.strictEqual(args.args[0].display_name, "A new type", "should create 'A new type'");
                    }
                    if (route === '/web/dataset/call_kw/partner/write') {
                        var commands = args.args[1].timmy;
                        assert.strictEqual(commands.length, 1, "should have generated one command");
                        assert.strictEqual(commands[0][0], 6, "generated command should be REPLACE WITH");
                        // get the created type's id
                        var createdType = _.findWhere(this.data.partner_type.records, {
                            display_name: "A new type"
                        });
                        var ids = _.sortBy([12, 15, 18].concat(createdType.id), _.identity.bind(_));
                        assert.ok(_.isEqual(_.sortBy(commands[0][2], _.identity.bind(_)), ids),
                            "new value should be " + ids);
                    }
                    return this._super.apply(this, arguments);
                },
            });

            // the SelectCreateDialog requests the session, so intercept its custom
            // event to specify a fake session to prevent it from crashing
            testUtils.mock.intercept(form, 'get_session', function (event) {
                event.data.callback({ user_context: {} });
            });

            assert.ok(!form.$('.o_kanban_view .delete_icon').length,
                'delete icon should not be visible in readonly');
            assert.ok(!form.$('.o_field_many2many .o-kanban-button-new').length,
                '"Add" button should not be visible in readonly');

            testUtils.form.clickEdit(form);

            assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2,
                'should contain 2 records');
            assert.strictEqual(form.$('.o_kanban_record:first() span').text(), 'gold',
                'display_name of subrecord should be the one in DB');
            assert.ok(form.$('.o_kanban_view .delete_icon').length,
                'delete icon should be visible in edit');
            assert.ok(form.$('.o_field_many2many .o-kanban-button-new').length,
                '"Add" button should be visible in edit');
            assert.strictEqual(form.$('.o_field_many2many .o-kanban-button-new').text().trim(), "Add",
                'Create button should have "Add" label');

            // edit existing subrecord
            testUtils.dom.click(form.$('.oe_kanban_global_click:first()'));

            testUtils.fields.editInput($('.modal .o_form_view input'), 'new name');
            testUtils.dom.click($('.modal .modal-footer .btn-primary'));
            assert.strictEqual(form.$('.o_kanban_record:first() span').text(), 'new name',
                'value of subrecord should have been updated');

            // add subrecords
            // -> single select
            testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new'));
            assert.ok($('.modal .o_list_view').length, "should have opened a list view in a modal");
            assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 3,
                "list view should contain 3 records");
            testUtils.dom.click($('.modal .o_list_view tbody tr:contains(red)'));
            assert.ok(!$('.modal .o_list_view').length, "should have closed the modal");
            assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 3,
                'kanban should now contain 3 records');
            assert.ok(form.$('.o_kanban_record:contains(red)').length,
                'record "red" should be in the kanban');

            // -> multiple select
            testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new'));
            assert.ok($('.modal .o_select_button').prop('disabled'), "select button should be disabled");
            assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 2,
                "list view should contain 2 records");
            testUtils.dom.click($('.modal .o_list_view thead .o_list_record_selector input'));
            testUtils.dom.click($('.modal .o_select_button'));
            assert.ok(!$('.modal .o_select_button').prop('disabled'), "select button should be enabled");
            assert.ok(!$('.modal .o_list_view').length, "should have closed the modal");
            assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5,
                'kanban should now contain 5 records');
            // -> created record
            testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new'));
            testUtils.dom.click($('.modal .modal-footer .btn-primary:nth(1)'));
            assert.ok($('.modal .o_form_view.o_form_editable').length,
                "should have opened a form view in edit mode, in a modal");
            testUtils.fields.editInput($('.modal .o_form_view input'), 'A new type');
            testUtils.dom.click($('.modal:nth(1) footer .btn-primary:first()'));
            assert.ok(!$('.modal').length, "should have closed both modals");
            assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 6,
                'kanban should now contain 6 records');
            assert.ok(form.$('.o_kanban_record:contains(A new type)').length,
                'the newly created type should be in the kanban');

            // delete subrecords
            testUtils.dom.click(form.$('.o_kanban_record:contains(silver)'));
            assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1,
                'There should be a modal having Remove Button');
            testUtils.dom.click($('.modal .modal-footer .o_btn_remove'));
            assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5,
                'should contain 5 records');
            assert.ok(!form.$('.o_kanban_record:contains(silver)').length,
                'the removed record should not be in kanban anymore');

            testUtils.dom.click(form.$('.o_kanban_record:contains(blue) .delete_icon'));
            assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 4,
                'should contain 4 records');
            assert.ok(!form.$('.o_kanban_record:contains(blue)').length,
                'the removed record should not be in kanban anymore');

            // save the record
            testUtils.form.clickSave(form);
            form.destroy();
        });

        QUnit.test('many2many kanban(editable): properly handle create_text node option', function (assert) {
            assert.expect(1);

            this.data.partner.records[0].timmy = [12];

            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="timmy" options="{\'create_text\': \'Add timmy\'}" mode="kanban">' +
                    '<kanban>' +
                    '<templates>' +
                    '<t t-name="kanban-box">' +
                    '<div class="oe_kanban_details">' +
                    '<field name="display_name"/>' +
                    '</div>' +
                    '</t>' +
                    '</templates>' +
                    '</kanban>' +
                    '</field>' +
                    '</form>',
                res_id: 1,
            });

            testUtils.form.clickEdit(form);
            assert.strictEqual(form.$('.o_field_many2many[name="timmy"] .o-kanban-button-new').text().trim(),
                "Add timmy", "In M2M Kanban, Add button should have 'Add timmy' label");

            form.destroy();
        });

        QUnit.test('many2many kanban: create action disabled', function (assert) {
            assert.expect(4);

            this.data.partner.records[0].timmy = [12, 14];

            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="timmy">' +
                    '<kanban create="0">' +
                    '<field name="display_name"/>' +
                    '<templates>' +
                    '<t t-name="kanban-box">' +
                    '<div class="oe_kanban_global_click">' +
                    '<a t-if="!read_only_mode" type="delete" class="fa fa-times float-right delete_icon"/>' +
                    '<span><t t-esc="record.display_name.value"/></span>' +
                    '</div>' +
                    '</t>' +
                    '</templates>' +
                    '</kanban>' +
                    '</field>' +
                    '</form>',
                archs: {
                    'partner_type,false,list': '<tree><field name="name"/></tree>',
                    'partner_type,false,search': '<search>' +
                        '<field name="display_name" string="Name"/>' +
                        '</search>',
                },
                res_id: 1,
                session: { user_context: {} },
            });

            assert.ok(!form.$('.o-kanban-button-new').length,
                '"Add" button should not be available in readonly');

            testUtils.form.clickEdit(form);

            assert.ok(form.$('.o-kanban-button-new').length,
                '"Add" button should be available in edit');
            assert.ok(form.$('.o_kanban_view .delete_icon').length,
                'delete icon should be visible in edit');

            testUtils.dom.click(form.$('.o-kanban-button-new'));
            assert.strictEqual($('.modal .modal-footer .btn-primary').length, 1, // only button 'Select'
                '"Create" button should not be available in the modal');

            form.destroy();
        });

        QUnit.test('many2many list (non editable): edition', function (assert) {
            assert.expect(27);

            this.data.partner.records[0].timmy = [12, 14];
            this.data.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 });
            this.data.partner_type.fields.float_field = { string: 'Float', type: 'float' };
            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="timmy">' +
                    '<tree>' +
                    '<field name="display_name"/><field name="float_field"/>' +
                    '</tree>' +
                    '<form string="Partners">' +
                    '<field name="display_name"/>' +
                    '</form>' +
                    '</field>' +
                    '</form>',
                archs: {
                    'partner_type,false,list': '<tree><field name="display_name"/></tree>',
                    'partner_type,false,search': '<search><field name="display_name"/></search>',
                },
                res_id: 1,
                mockRPC: function (route, args) {
                    if (args.method !== 'load_views') {
                        assert.step(_.last(route.split('/')));
                    }
                    if (args.method === 'write' && args.model === 'partner') {
                        assert.deepEqual(args.args[1].timmy, [
                            [6, false, [12, 15]],
                        ]);
                    }
                    return this._super.apply(this, arguments);
                },
            });

            assert.ok(!form.$('.o_list_record_remove').length,
                'delete icon should not be visible in readonly');
            assert.ok(!form.$('.o_field_x2many_list_row_add').length,
                '"Add an item" should not be visible in readonly');

            testUtils.form.clickEdit(form);

            assert.containsN(form, '.o_list_view td.o_list_number', 2,
                'should contain 2 records');
            assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'gold',
                'display_name of first subrecord should be the one in DB');
            assert.ok(form.$('.o_list_record_remove').length,
                'delete icon should be visible in edit');
            assert.ok(form.$('.o_field_x2many_list_row_add').length,
                '"Add an item" should be visible in edit');

            // edit existing subrecord
            testUtils.dom.click(form.$('.o_list_view tbody tr:first()'));

            testUtils.fields.editInput($('.modal .o_form_view input'), 'new name');
            testUtils.dom.click($('.modal .modal-footer .btn-primary'));
            assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
                'value of subrecord should have been updated');

            // add new subrecords
            testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
            assert.strictEqual($('.modal .o_list_view').length, 1,
                "a modal should be open");
            assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1,
                "the list should contain one row");
            testUtils.dom.click($('.modal .o_list_view .o_data_row'));
            assert.strictEqual($('.modal .o_list_view').length, 0,
                "the modal should be closed");
            assert.containsN(form, '.o_list_view td.o_list_number', 3,
                'should contain 3 subrecords');

            // remove subrecords
            testUtils.dom.click(form.$('.o_list_record_remove:nth(1)'));
            assert.containsN(form, '.o_list_view td.o_list_number', 2,
                'should contain 2 subrecords');
            assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), 'new name',
                'the updated row still has the correct values');

            // save
            testUtils.form.clickSave(form);
            assert.containsN(form, '.o_list_view td.o_list_number', 2,
                'should contain 2 subrecords');
            assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(),
                'new name', 'the updated row still has the correct values');

            assert.verifySteps([
                'read', // main record
                'read', // relational field
                'read', // relational record in dialog
                'write', // save relational record from dialog
                'read', // relational field (updated)
                'search_read', // list view in dialog
                'read', // relational field (updated)
                'write', // save main record
                'read', // main record
                'read', // relational field
            ]);

            form.destroy();
        });

        QUnit.test('many2many list (editable): edition', function (assert) {
            assert.expect(31);

            this.data.partner.records[0].timmy = [12, 14];
            this.data.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 });
            this.data.partner_type.fields.float_field = { string: 'Float', type: 'float' };
            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="timmy">' +
                    '<tree editable="top">' +
                    '<field name="display_name"/><field name="float_field"/>' +
                    '</tree>' +
                    '</field>' +
                    '</form>',
                archs: {
                    'partner_type,false,list': '<tree><field name="display_name"/></tree>',
                    'partner_type,false,search': '<search><field name="display_name"/></search>',
                },
                mockRPC: function (route, args) {
                    if (args.method !== 'load_views') {
                        assert.step(_.last(route.split('/')));
                    }
                    if (args.method === 'write') {
                        assert.deepEqual(args.args[1].timmy, [
                            [6, false, [12, 15]],
                            [1, 12, { display_name: 'new name' }],
                        ]);
                    }
                    return this._super.apply(this, arguments);
                },
                res_id: 1,
            });

            assert.ok(!form.$('.o_list_record_remove').length,
                'delete icon should not be visible in readonly');
            assert.ok(!form.$('.o_field_x2many_list_row_add').length,
                '"Add an item" should not be visible in readonly');

            testUtils.form.clickEdit(form);

            assert.containsN(form, '.o_list_view td.o_list_number', 2,
                'should contain 2 records');
            assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'gold',
                'display_name of first subrecord should be the one in DB');
            assert.ok(form.$('.o_list_record_remove').length,
                'delete icon should be visible in edit');
            assert.hasClass(form.$('td.o_list_record_remove button').first(),'fa fa-times',
                "should have X icons to remove (unlink) records");
            assert.ok(form.$('.o_field_x2many_list_row_add').length,
                '"Add an item" should not visible in edit');

            // edit existing subrecord
            testUtils.dom.click(form.$('.o_list_view tbody td:first()'));
            assert.ok(!$('.modal').length,
                'in edit, clicking on a subrecord should not open a dialog');
            assert.hasClass(form.$('.o_list_view tbody tr:first()'),'o_selected_row',
                'first row should be in edition');
            testUtils.fields.editInput(form.$('.o_list_view input:first()'), 'new name');
            assert.hasClass(form.$('.o_list_view .o_data_row:first'),'o_selected_row',
                'first row should still be in edition');
            assert.strictEqual(form.$('.o_list_view input[name=display_name]').get(0),
                document.activeElement, 'edited field should still have the focus');
            testUtils.dom.click(form.$el);
            assert.doesNotHaveClass(form.$('.o_list_view tbody tr:first'), 'o_selected_row',
                'first row should not be in edition anymore');
            assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
                'value of subrecord should have been updated');
            assert.verifySteps(['read', 'read']);

            // add new subrecords
            testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
            assert.strictEqual($('.modal .o_list_view').length, 1,
                "a modal should be open");
            assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1,
                "the list should contain one row");
            testUtils.dom.click($('.modal .o_list_view .o_data_row'));
            assert.strictEqual($('.modal .o_list_view').length, 0,
                "the modal should be closed");
            assert.containsN(form, '.o_list_view td.o_list_number', 3,
                'should contain 3 subrecords');

            // remove subrecords
            testUtils.dom.click(form.$('.o_list_record_remove:nth(1)'));
            assert.containsN(form, '.o_list_view td.o_list_number', 2,
                'should contain 2 subrecord');
            assert.strictEqual(form.$('.o_list_view tbody .o_data_row td:first').text(),
                'new name', 'the updated row still has the correct values');

            // save
            testUtils.form.clickSave(form);
            assert.containsN(form, '.o_list_view td.o_list_number', 2,
                'should contain 2 subrecords');
            assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(),
                'new name', 'the updated row still has the correct values');

            assert.verifySteps([
                'read', // main record
                'read', // relational field
                'search_read', // list view in dialog
                'read', // relational field (updated)
                'write', // save main record
                'read', // main record
                'read', // relational field
            ]);

            form.destroy();
        });

        QUnit.test('many2many: create & delete attributes', function (assert) {
            assert.expect(4);

            this.data.partner.records[0].timmy = [12, 14];

            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="timmy">' +
                    '<tree create="true" delete="true">' +
                    '<field name="color"/>' +
                    '</tree>' +
                    '</field>' +
                    '</form>',
                res_id: 1,
            });

            testUtils.form.clickEdit(form);

            assert.containsOnce(form, '.o_field_x2many_list_row_add', "should have the 'Add an item' link");
            assert.containsN(form, '.o_list_record_remove', 2, "should have the 'Add an item' link");

            form.destroy();

            form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="timmy">' +
                    '<tree create="false" delete="false">' +
                    '<field name="color"/>' +
                    '</tree>' +
                    '</field>' +
                    '</form>',
                res_id: 1,
            });

            testUtils.form.clickEdit(form);

            assert.containsNone(form, '.o_field_x2many_list_row_add', "should not have the 'Add an item' link");
            assert.containsNone(form, '.o_list_record_remove', "should not have the 'Add an item' link");

            form.destroy();
        });

        QUnit.test('many2many list: create action disabled', function (assert) {
            assert.expect(2);
            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="timmy">' +
                    '<tree create="0">' +
                    '<field name="name"/>' +
                    '</tree>' +
                    '</field>' +
                    '</form>',
                res_id: 1,
            });

            assert.ok(!form.$('.o_field_x2many_list_row_add').length,
                '"Add an item" link should not be available in readonly');

            testUtils.form.clickEdit(form);

            assert.ok(!form.$('.o_field_x2many_list_row_add').length,
                '"Add an item" link should not be available in edit either');

            form.destroy();
        });

        QUnit.test('many2many list: list of id as default value', function (assert) {
            assert.expect(1);

            this.data.partner.fields.turtles.default = [2, 3];
            this.data.partner.fields.turtles.type = "many2many";

            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="turtles">' +
                    '<tree>' +
                    '<field name="turtle_foo"/>' +
                    '</tree>' +
                    '</field>' +
                    '</form>',
            });

            assert.strictEqual(form.$('td.o_data_cell').text(), "blipkawa",
                "should have loaded default data");

            form.destroy();
        });

        QUnit.test('many2many checkboxes with default values', function (assert) {
            assert.expect(7);

            this.data.partner.fields.turtles.default = [3];
            this.data.partner.fields.turtles.type = "many2many";

            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="turtles" widget="many2many_checkboxes">' +
                    '</field>' +
                    '</form>',
                mockRPC: function (route, args) {
                    if (args.method === 'create') {
                        assert.deepEqual(args.args[0].turtles, [[6, false, [1]]],
                            "correct values should have been sent to create");
                    }
                    return this._super.apply(this, arguments);
                }
            });

            assert.notOk(form.$('.o_form_view .custom-checkbox input').eq(0).prop('checked'),
                "first checkbox should not be checked");
            assert.notOk(form.$('.o_form_view .custom-checkbox input').eq(1).prop('checked'),
                "second checkbox should not be checked");
            assert.ok(form.$('.o_form_view .custom-checkbox input').eq(2).prop('checked'),
                "third checkbox should be checked");

            testUtils.dom.click(form.$('.o_form_view .custom-checkbox input:checked'));
            testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first());
            testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first());
            testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first());

            assert.ok(form.$('.o_form_view .custom-checkbox input').eq(0).prop('checked'),
                "first checkbox should be checked");
            assert.notOk(form.$('.o_form_view .custom-checkbox input').eq(1).prop('checked'),
                "second checkbox should not be checked");
            assert.notOk(form.$('.o_form_view .custom-checkbox input').eq(2).prop('checked'),
                "third checkbox should not be checked");

            testUtils.form.clickSave(form);

            form.destroy();
        });

        QUnit.test('many2many list with x2many: add a record', function (assert) {
            assert.expect(18);

            this.data.partner_type.fields.m2m = {
                string: "M2M", type: "many2many", relation: 'turtle',
            };
            this.data.partner_type.records[0].m2m = [1, 2];
            this.data.partner_type.records[1].m2m = [2, 3];

            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="timmy"/>' +
                    '</form>',
                res_id: 1,
                archs: {
                    'partner_type,false,list': '<tree>' +
                        '<field name="display_name"/>' +
                        '<field name="m2m" widget="many2many_tags"/>' +
                        '</tree>',
                    'partner_type,false,search': '<search>' +
                        '<field name="display_name" string="Name"/>' +
                        '</search>',
                },
                mockRPC: function (route, args) {
                    if (args.method !== 'load_views') {
                        assert.step(_.last(route.split('/')) + ' on ' + args.model);
                    }
                    if (args.model === 'turtle') {
                        assert.step(args.args[0]); // the read ids
                    }
                    return this._super.apply(this, arguments);
                },
                viewOptions: {
                    mode: 'edit',
                },
            });

            testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
            testUtils.dom.click($('.modal .o_data_row:first'));

            assert.containsOnce(form, '.o_data_row',
                "the record should have been added to the relation");
            assert.strictEqual(form.$('.o_data_row:first .o_badge_text').text(), 'leonardodonatello',
                "inner m2m should have been fetched and correctly displayed");

            testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
            testUtils.dom.click($('.modal .o_data_row:first'));

            assert.containsN(form, '.o_data_row', 2,
                "the second record should have been added to the relation");
            assert.strictEqual(form.$('.o_data_row:nth(1) .o_badge_text').text(), 'donatelloraphael',
                "inner m2m should have been fetched and correctly displayed");

            assert.verifySteps([
                'read on partner',
                'search_read on partner_type',
                'read on turtle',
                [1, 2, 3],
                'read on partner_type',
                'read on turtle',
                [1, 2],
                'search_read on partner_type',
                'read on turtle',
                [2, 3],
                'read on partner_type',
                'read on turtle',
                [2, 3],
            ]);

            form.destroy();
        });

        QUnit.test('many2many with a domain', function (assert) {
            // The domain specified on the field should not be replaced by the potential
            // domain the user writes in the dialog, they should rather be concatenated
            assert.expect(2);

            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="timmy" domain="[[\'display_name\', \'=\', \'gold\']]"/>' +
                    '</form>',
                res_id: 1,
                archs: {
                    'partner_type,false,list': '<tree>' +
                        '<field name="display_name"/>' +
                        '</tree>',
                    'partner_type,false,search': '<search>' +
                        '<field name="display_name" string="Name"/>' +
                        '</search>',
                },
                viewOptions: {
                    mode: 'edit',
                },
            });

            testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
            assert.strictEqual($('.modal .o_data_row').length, 1,
                "should contain only one row (gold)");

            $('.modal .o_searchview_input').trigger({ type: 'keypress', which: 115 }); // s
            $('.modal .o_searchview_input').trigger({ type: 'keydown', which: 13 }); // enter

            assert.strictEqual($('.modal .o_data_row').length, 0, "should contain no row");

            form.destroy();
        });

        QUnit.test('many2many list with onchange and edition of a record', function (assert) {
            assert.expect(8);

            this.data.partner.fields.turtles.type = "many2many";
            this.data.partner.onchanges.turtles = function () { };
            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="turtles">' +
                    '<tree>' +
                    '<field name="turtle_foo"/>' +
                    '</tree>' +
                    '</field>' +
                    '</form>',
                res_id: 1,
                archs: {
                    'turtle,false,form': '<form string="Turtle Power"><field name="turtle_bar"/></form>',
                },
                mockRPC: function (route, args) {
                    assert.step(args.method);
                    return this._super.apply(this, arguments);
                },
            });

            testUtils.form.clickEdit(form);
            testUtils.dom.click(form.$('td.o_data_cell:first'));

            testUtils.dom.click($('.modal-body input[type="checkbox"]'));
            testUtils.dom.click($('.modal .modal-footer .btn-primary').first());

            // there is nothing left to save -> should not do a 'write' RPC
            testUtils.form.clickSave(form);

            assert.verifySteps([
                'read', // read initial record (on partner)
                'read', // read many2many turtles
                'load_views', // load arch of turtles form view
                'read', // read missing field when opening record in modal form view
                'write', // when saving the modal
                'onchange', // onchange should be triggered on partner
                'read', // reload many2many
            ]);

            form.destroy();
        });

        QUnit.test('onchange with 40+ commands for a many2many', function (assert) {
            // this test ensures that the basic_model correctly handles more LINK_TO
            // commands than the limit of the dataPoint (40 for x2many kanban)
            assert.expect(23);

            // create a lot of partner_types that will be linked by the onchange
            var commands = [[5]];
            for (var i = 0; i < 45; i++) {
                var id = 100 + i;
                this.data.partner_type.records.push({ id: id, display_name: "type " + id });
                commands.push([4, id]);
            }
            this.data.partner.onchanges = {
                foo: function (obj) {
                    obj.timmy = commands;
                },
            };

            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="foo"/>' +
                    '<field name="timmy">' +
                    '<kanban>' +
                    '<field name="display_name"/>' +
                    '<templates>' +
                    '<t t-name="kanban-box">' +
                    '<div><t t-esc="record.display_name.value"/></div>' +
                    '</t>' +
                    '</templates>' +
                    '</kanban>' +
                    '</field>' +
                    '</form>',
                res_id: 1,
                mockRPC: function (route, args) {
                    assert.step(args.method);
                    if (args.method === 'write') {
                        assert.strictEqual(args.args[1].timmy[0][0], 6,
                            "should send a command 6");
                        assert.strictEqual(args.args[1].timmy[0][2].length, 45,
                            "should replace with 45 ids");
                    }
                    return this._super.apply(this, arguments);
                },
                viewOptions: {
                    mode: 'edit',
                },
            });

            assert.verifySteps(['read']);

            testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'trigger onchange');

            assert.verifySteps(['read', 'onchange', 'read']);
            assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
                '1-40 / 45', "pager should be correct");
            assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
                'there should be 40 records displayed on page 1');

            testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next'));
            assert.verifySteps(['read', 'onchange', 'read', 'read']);
            assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
                '41-45 / 45', "pager should be correct");
            assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5,
                'there should be 5 records displayed on page 2');

            testUtils.form.clickSave(form);

            assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
                '1-40 / 45', "pager should be correct");
            assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
                'there should be 40 records displayed on page 1');

            testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next'));
            assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
                '41-45 / 45', "pager should be correct");
            assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5,
                'there should be 5 records displayed on page 2');

            testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next'));
            assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(),
                '1-40 / 45', "pager should be correct");
            assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40,
                'there should be 40 records displayed on page 1');

            form.destroy();
        });

        QUnit.test('default_get, onchange, onchange on m2m', function (assert) {
            assert.expect(1);

            this.data.partner.onchanges.int_field = function (obj) {
                if (obj.int_field === 2) {
                    assert.deepEqual(obj.timmy, [
                        [6, false, [12]],
                        [1, 12, { display_name: 'gold' }]
                    ]);
                }
                obj.timmy = [
                    [5],
                    [1, 12, { display_name: 'gold' }]
                ];
            };

            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form>' +
                    '<sheet>' +
                    '<field name="timmy">' +
                    '<tree>' +
                    '<field name="display_name"/>' +
                    '</tree>' +
                    '</field>' +
                    '<field name="int_field"/>' +
                    '</sheet>' +
                    '</form>',
            });

            testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2);
            form.destroy();
        });

        QUnit.test('widget many2many_tags', function (assert) {
            assert.expect(1);
            this.data.turtle.records[0].partner_ids = [2];

            var form = createView({
                View: FormView,
                model: 'turtle',
                data: this.data,
                arch: '<form string="Turtles">' +
                    '<sheet>' +
                    '<field name="display_name"/>' +
                    '<field name="partner_ids" widget="many2many_tags"/>' +
                    '</sheet>' +
                    '</form>',
                res_id: 1,
            });

            assert.deepEqual(
                form.$('.o_field_many2manytags.o_field_widget .badge .o_badge_text').attr('title'),
                'second record', 'the title should be filled in'
            );

            form.destroy();
        });

        QUnit.test('many2many list add *many* records, remove, re-add', function (assert) {
            assert.expect(5);

            this.data.partner.fields.timmy.domain = [['color', '=', 2]];
            this.data.partner.fields.timmy.onChange = true;
            this.data.partner_type.fields.product_ids = { string: "Product", type: "many2many", relation: 'product' };

            for (var i = 0; i < 50; i++) {
                var new_record_partner_type = { id: 100 + i, display_name: "batch" + i, color: 2 };
                this.data.partner_type.records.push(new_record_partner_type);
            }

            var form = createView({
                View: FormView,
                model: 'partner',
                data: this.data,
                arch: '<form string="Partners">' +
                    '<field name="timmy" widget="many2many">' +
                    '<tree>' +
                    '<field name="display_name"/>' +
                    '<field name="product_ids" widget="many2many_tags"/>' +
                    '</tree>' +
                    '</field>' +
                    '</form>',
                res_id: 1,
                archs: {
                    'partner_type,false,list': '<tree><field name="display_name"/></tree>',
                    'partner_type,false,search': '<search><field name="display_name"/><field name="color"/></search>',
                },
                mockRPC: function (route, args) {
                    if (args.method === 'get_formview_id') {
                        assert.deepEqual(args.args[0], [1], "should call get_formview_id with correct id");
                        return $.when(false);
                    }
                    return this._super(route, args);
                },
            });

            // First round: add 51 records in batch
            testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_edit'));
            testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));

            var $modal = $('.modal-lg');

            assert.equal($modal.length, 1,
                'There should be one modal');

            testUtils.dom.click($modal.find('thead input[type=checkbox]'));

            testUtils.dom.click($modal.find('.btn.btn-primary.o_select_button'));

            var m2m_records = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_data_cell:not(.o_many2many_tags_cell)');
            assert.equal(m2m_records.length, 51,
                'We should have added all the records present in the search view to the m2m field'); // the 50 in batch + 'gold'

            testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_save'));

            // Secound round: remove one record
            testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_edit'));
            var trash_buttons = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_list_record_remove');

            testUtils.dom.click(trash_buttons.first());

            var pager_limit = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_pager_limit');
            assert.equal(pager_limit.text(), '50',
                'We should have 50 records in the m2m field');

            // Third round: re-add 1 records
            testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));

            $modal = $('.modal-lg');

            assert.equal($modal.length, 1,
                'There should be one modal');

            testUtils.dom.click($modal.find('thead input[type=checkbox]'));

            testUtils.dom.click($modal.find('.btn.btn-primary.o_select_button'));

            pager_limit = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_pager_limit');
            assert.equal(pager_limit.text(), '51',
                'We should have 51 records in the m2m field');

            form.destroy();
        });
    });
});
});
