Рубрики
Без рубрики

Создайте поле odoo 14 Markdown Widget с TDD – Часть 2

Вступление Это вторая часть серии статьи, где мы используем TDD для разработки … Tag с TDD, JavaScript, Odoo, учебным пособием.

Вступление

Это Вторая часть серии статьи, где мы используем TDD для разработки виджета Odoo Markdown.

Создайте поле odoo 14 Markdown Widget с TDD – Часть 1

В последней части (код доступен здесь ) Мы получили функциональный виджет, преобразующий содержание чистого текста в HTML в режиме рендеринга и ведя себя как стандартный FieldText в режиме редактирования.

В этом уроке мы собираемся использовать редактор Simplemde вместо стандартного FieldText вход.

Рефакторирование и добавление новых тестов

Прежде всего, мы собираемся Удалить тест названный web_widget_markdown redit form Анкет В качестве напоминания, этот тест использовался для редактирования формы и записи на ввод так:

await testUtils.form.clickEdit(form);
await testUtils.fields.editInput(form.$('.o_field_markdown'), ' **bold content**');
await testUtils.form.clickSave(form);

Проблема в том, что Редакция Функция больше не будет работать, потому что Simplemde заменит весь С его собственным редактором и написанием внутри не будет возможно.

Как проверить присутствие Simplemde

Чтобы проверить наличие SimpleMde, мы должны проанализировать, как эта библиотека вставила своего редактора в DOM, и быстрое осмотр дает нам больше информации:

...

Как мы видим, Simplemde использует базовую библиотеку Codemirror создать своего редактора. Так что проверить наличие div с классом .Codemirror следует подтвердить наличие редактора. Давайте напишем новый тест.

QUnit.test('web_widget_markdown SimpleMDE is present', async function(assert) {
    assert.expect(1);
    var form = await testUtils.createView({
        View: FormView,
        model: 'blog',
        data: this.data,
        arch: '
' + '' + '' + '' + '' + '
', res_id: 1, }); await testUtils.form.clickEdit(form); assert.strictEqual( form.$('.o_field_markdown').find("div.CodeMirror").length, 1, "CodeMirror div should be present" ) form.destroy(); });

Как проверить, что Simplemde работает

Чтобы проверить, что простой MDE работает, мы должны:

  • Во -первых, перейдите в режим редактирования, чтобы Simplemde инициализирован (предыдущий тест)
  • Убедитесь, что начальное значение нашей модели (данных) передается в SimpleMde
  • Измените ценность контента SimpleMde (издевательское поведение пользователя в Wysiwyg) и убедитесь, что это Odoo widget value было обновлено
  • Сохраните форму и утверждайте, что наши изменения сохраняются и присутствуют

Написание тестов

Чтобы пройти через наш тест, нам нужно будет иметь доступ к самому виджету Из издевательной формы. Объект формы имеет рендерер Атрибут, который будет полезен в этой ситуации, осматривая его Allfieldwidgets свойство:

// [1] because in our form the first field is for the name of the blog
// So the first field is in [0] and ours is in [1]
var markdownField = _.find(form.renderer.allFieldWidgets)[1];

Внутри теста мы хотим иметь возможность иметь доступ к Экземпляр Simplemde непосредственно из виджета .

Часто, Мы пишем тесты, которые заставляют нас реализовать решение определенным образом Анкет В этом примере мы знаем, что мы хотим, чтобы объект виджета удерживал объект свойства с именем Simplemde содержащий текущий экземпляр Новый Simplemde Редактор. Это поможет нам инициализировать его, уничтожить его, установить или получить его ценность. Это мощный способ программирования, потому что тест помогает нам сделать более надежные API, непосредственно нуждаясь в реализации строгих необходимых функций, чтобы он был функциональным.

Поэтому, учитывая эту идею, у нас есть это свойство, тест может быть написан так

QUnit.test('web_widget_markdown edit SimpleMDE', async function(assert) {
    assert.expect(4);
    var form = await testUtils.createView({
        View: FormView,
        model: 'blog',
        data: this.data,
        arch: '
' + '' + '' + '' + '' + '
', res_id: 1, }); await testUtils.form.clickEdit(form); var markdownField = _.find(form.renderer.allFieldWidgets)[1]; assert.strictEqual( markdownField.simplemde.value(), "# Hello world", "Initial Value of SimpleMDE should be set" ) markdownField.simplemde.value(' **bold content**'); assert.strictEqual( markdownField._getValue(), " **bold content**", "If we change value in SimpleMDE, value of odoo widget should be updated" ) await testUtils.form.clickSave(form); assert.strictEqual( form.$('.o_field_markdown').find("strong").length, 1, "After Save, b should be present" ) assert.strictEqual( form.$('.o_field_markdown strong').text(), "bold content", "After Save, should contain 'bold content'" ) form.destroy(); });

Мы не можем должным образом взаимодействовать с редактором Codemirror с jquery testutils, поэтому мы будем ссылаться на Руководство пользователя Codemirror Чтобы увидеть, как вставить значение (это также то, что происходит, когда пользователь тип), и именно так мы сделаем это из функции теста:

markdownField.simplemde.codemirror.setValue(' **bold content**');

И проверить это Odoo Field Сама имеет то же значение, что и редактор Markdown, мы делаем это утверждение.

assert.strictEqual(
    markdownField._getValue(), 
    " **bold content**", 
    "Value of odoo widget should be updated"
)

Общие знания: понимание _getValue () в виджете Odoo

_getValue () функция, сначала определенная в Debouncedfield (FieldTex Thishorits Debouncedfield) .

// Inside DebouncedField in odoo/addons/web/static/src/js/fields/basic_fields.js
/**
 * Should return the current value of the field, in the DOM (for example,
 * the content of the input)
 *
 * @abstract
 * @private
 * @returns {*}
 */
_getValue: function () {},

A Debouncedfield это суперкласс, который обрабатывает дезакширование пользовательского ввода.

Дебаун ввода в JavaScript является общим методом для снижения скорости выполнения функции. Если пользователь вводит внутри ввода, и вы выполняете функцию при каждом изменении этого ввода (каждая типичная буква), он может быстро привести к тому, что на этом используется много вычислительной мощности. Общая методика называется Debounked, и он задержит выполнение функции, прослушивая вход, только каждые x Secentes. ODOO Использование _.debounce для этого с BIBITORD НОМЕРСКОРА Анкет

Это суммированное представление о графе Widget Fields Fields Fields

// the super class
var AbstractField = {}
    // handle debouncing
    var DebouncedField = AbstractField.extend({})
        // handle keystroke evnts, state and other things
        var InputField = DebouncedField.extend({})
            // more specific implementations using InputField logic
            var FieldText = InputField.extend({})
            var FieldChar = InputField.extend({})
            var FieldDate = InputField.extend({})
            var FieldDate = InputField.extend({})

Большая часть поля наследуя входные проведения переопределяет это _getValue () Функция возврата больше, чем основное this.value Свойство виджета, и мы сделаем то же самое.

Запуск тестов в текущем состоянии нашего виджета, как ожидается, не удалось.

Инициализировать редактор Simplemde в режиме редактирования виджетов

Когда мы писали наши тесты ранее, мы знаем, что нам нужно иметь Simplemde Как свойство нашего виджета, давайте продлим init Функция нашего виджета делает это:

/**
* @constructor
*/
init: function () {
    this._super.apply(this, arguments);
    this.simplemde = {}
},

Прикрепление SimpleMde к нашему корневому элементу виджета DOM.

И в старт функция (Доступно во всех виджетах ODOO) Мы сделаем это:

/**
 * When the the widget render, check view mode, if edit we
 * instanciate our SimpleMDE
 * 
 * @override
 */
start: function () {
    if (this.mode === 'edit') {
        this.simplemde = new SimpleMDE({element: this.$el[0]});
    }
    return this._super();
},

Когда мы создаем экземпляр Simplemde, нам нужно хотя бы дать ему элемент вариант, иначе он будет прикрепляться к любому Существующий (Это поведение библиотеки по умолчанию) .

Что это. $ el [0] ?

это. $ el это объект jQuery и не чистый доми Элемент, как это требуется SimpleMde, так что делая это. $ el [0] Мы получаем правильный элемент DOM.

Имейте в виду, что мы наследуем FieldText, и FieldText имеет некоторую исходную логику о элементе HTML, который он использует для самого визуализации. В режиме только для чтения это и в режиме редактирования изменение тега, как видно здесь в исходный код из FieldText :

/**
* @constructor
*/
init: function () {
    this._super.apply(this, arguments);

    if (this.mode === 'edit') {
        this.tagName = 'textarea';
    }
}

Запуск тестов и анализ ошибки

Теперь, если мы запустим тесты, мы увидим эту ошибку

  1. Не удается прочитать свойство «Вставка перед» null@ 121 мс
TypeError: Cannot read property 'insertBefore' of null
    at http://localhost:8069/web_widget_markdown/static/lib/simplemde.min.js:12:1240
    at new t (http://localhost:8069/web_widget_markdown/static/lib/simplemde.min.js:7:31640)
    at new e (http://localhost:8069/web_widget_markdown/static/lib/simplemde.min.js:7:29476)
    at e (http://localhost:8069/web_widget_markdown/static/lib/simplemde.min.js:7:29276)
    at Function.e.fromTextArea (http://localhost:8069/web_widget_markdown/static/lib/simplemde.min.js:12:1213)
    at B.render (http://localhost:8069/web_widget_markdown/static/lib/simplemde.min.js:15:4157)
    at new B (http://localhost:8069/web_widget_markdown/static/lib/simplemde.min.js:14:28861)
    at Class.start (http://localhost:8069/web_widget_markdown/static/src/js/field_widget.js:34:30)
    at Class.prototype. [as start] (http://localhost:8069/web/static/src/js/core/class.js:90:38)
    at http://localhost:8069/web/static/src/js/core/widget.js:440:25

Ошибка на самом деле возникает Из библиотеки Simplemde пытаясь вставить себя в DOM. Мы дали ему $ el [0] как элемент. И, как видно в исходном коде, фактический указанный элемент – это , это связано с тем, что мы унаследованы FieldText.

Но проблема на самом деле происходит от окружения элемент. Simplemde действительно будет использовать ParentNode на элементе, данном для того, чтобы поместить себя. Элемент, приведенный как $ el [0] как есть Нет родителей Из -за того, как Odoo Framework вставляет его в DOM.

Итак, база Шаблон нашего поля не может быть таким простым, как промежуток, оно должно быть инкапсулировано другим DIV или чем -то еще.

Переход к выделенному шаблону QWEB для нашего виджета

Чтобы создать шаблон для виджета, нам необходимо создать файл XML, содержащий наш шаблон, а затем явно используйте его в нашем объявлении виджета JavaScript.

Шаблон QWEB

Создайте файл static/src/xml/qweb_template.xml с этим контентом.



    
        

Мы дали наш шаблон то же имя t-name = "Fieldmarkdown как имя, которое мы экспортируем в нашем файле JavaScript для согласованности. Внутри это просто класс Div с тем же классе с одним и тем же классом .o_field_markdown Мы использовали до и внутри него Для Simplemde прикрепить к.

Добавьте его в свой __manifest__.py

"qweb": [ 
    'static/src/xml/qweb_template.xml',
],

Использование шаблона в нашем виджете поля JavaScript

var markdownField = basicFields.FieldText.extend({
    supportedFieldTypes: ['text'],
    // className: 'o_field_markdown',
    template: 'FieldMarkdown', // name of template in xml Qweb file
    jsLibs: [
        '/web_widget_markdown/static/lib/simplemde.min.js',
    ],
    // ...

Мы удалили ClassName атрибут, потому что это больше не полезно.

Запустите тесты снова, и, конечно, это снова выйдет из строя, потому что мы все еще говорим Simplemde, чтобы прикрепить себя к корень $ EL нашего виджета.

Рефакторирование нашего виджета для использования нового шаблона

Внутри начала функции виджета мы будем нацелены на внутри

Мы создали в шаблоне.

start: function () {
    if (this.mode === 'edit') {
        var $textarea = this.$el.find('textarea');
        this.simplemde = new SimpleMDE({element: $textarea[0]});
    }
    return this._super();
},

Теперь, если мы снова запустим тесты:

  • Тесты виджета Markdown: web_widget_markdown simplemde присутствует (1)
  • Тесты виджета Markdown: web_widget_markdown edit simplemde (3, 0, 3) ❌

Это означает Наш Simplemde хорошо инициализирован Но нет никакого сообщения о значении между виджетом и редактором Simplemde.

Связь между SimpleMde и виджет

Инициализировать Simplemde с значением данных

Первый тест, который мы попытаемся пройти, это Начальное значение Simplemde должно быть установлено. Для этого мы будем ссылаться на Simplemde документация по настройке и получению значения Анкет

Мы видим, что есть простой метод set ("value") но также ИСТИНАЛЬНАЯ ВАЛА Это может быть передано в экземпляре. Мы выберем второе решение и внесем эти изменения в старт Функция нашего виджета:

start: function () {
    if (this.mode === 'edit') {
        var $textarea = this.$el.find('textarea');
        this.simplemde = new SimpleMDE({
            element: $textarea[0],
            initialValue: this.value, // this.value represents widget data
        });
    }
    return this._super();
},

Теперь мы снова запускаем тесты и наверняка видим, что наш первый тест прошел ✅

В первой части мы справились с _renderReadonly Функция, теперь, когда мы работаем в режиме редактирования, мы переопределим функцию _renderedit Чтобы установить значение в SimpleMde, добавьте эти методы в виджет

    _formatValue: function (value) {
        return this._super.apply(this, arguments) || '';
    },

    _renderEdit: function () {
        this._super.apply(this, arguments);
        var newValue = this._formatValue(this.value);
        if (this.simplemde.value() !== newValue) {
            this.simplemde.value(newValue);
        }
    },

Simplemde Не могу справиться с ложным или нулевым значением Итак, функция _formatValue Есть ли, чтобы помочь нам вернуть пустую строку, когда в поле ничего нет.

_renderedit и _renderReadonly называются главным _render функция, которая определена в odoo/addons/web/static/src/js/fields/rablect_field.js Анкет Эта основная функция рендеринга обрабатывает условную логику виджета в режиме редактирования или чтения и вызовите правильную функцию:


    _render: function () {
        if (this.attrs.decorations) {
            this._applyDecorations();
        }
        if (this.mode === 'edit') {
            return this._renderEdit();
        } else if (this.mode === 'readonly') {
            return this._renderReadonly();
        }
    },


Мы снова запускаем тесты, и все все еще зеленое ✅, чтобы мы могли перейти к следующему шагу.

Прослушивание изменения в SimpleMde, чтобы обновить значение нашего виджета.

В нашем предыдущем тесте мы написали это markdownfield._getvalue () Должен быть равен тому, что мы пишем внутри редактора Simplemde.

Естественно мы добавим это _getValue () функционируйте и сделайте его возвратом внутреннего значения SimpleMde.

/**
 * return the SimpleMDE value
 *
 * @private
 */
_getValue: function () {
    return this.simplemde.value();
},

Поскольку у нас есть доступ к собственности Simplemde Что мы инициализируем в нашем виджете, очень легко получить данные.

Затем, чтобы прослушать изменения, мы должны получить экземпляр Codemirror нашего Simplemde и прослушать его Изменение События, которые запускает Codemirror.

start: function () {
    if (this.mode === 'edit') {
        var $textarea = this.$el.find('textarea');
        this.simplemde = new SimpleMDE({
            element: $textarea[0],
            initialValue: this.value,
        });
        var self = this;
        this.simplemde.codemirror.on("change", function(){
            self._setValue(self.simplemde.value());
        })
    }
    return this._super();
},

Мы должны были объявить вар иметь возможность использовать его в функции обратного вызова.

С этим изменением давайте снова запустим тесты

  1. Начальное значение SimpleMde должно быть установлено при 75 мс ✅ ✅
  2. Если мы изменим значение в SimpleMde, значение виджета Odoo должно быть обновлено при 81 мс ✅
  3. После сохранения B должен присутствовать@ 380 мс ✅
  4. После сохранения, должен содержать «смелый контент» ✅

Победа!

Теперь, когда наши тесты прошли, мы можем попробовать модуль с точки зрения пользователя

К сожалению, мы видим, что есть некоторая проблема с аспектом нашего редактора Markdown.

Кажется, что высота установлена, поэтому для этого недостаточно места. Это происходит из -за того, что мы расширяем Виджет FieldTex и имеет встроенные функции автоматического разрешения Анкет

В следующей части мы увидим, как с этим справиться, когда мы улучшаем наш виджет.

Исходный код для этого урока доступен здесь на GitHub Анкет

✨ Обновление 17/06/2021 🎓 Третья часть теперь доступна здесь

Спасибо за чтение, если вам понравилась эта статья, пожалуйста, подумайте:

Оригинал: “https://dev.to/codingdodo/create-an-odoo-14-markdown-widget-field-with-tdd-part-2-23f”