diff --git a/docs/content/en/api/binders/progress.md b/docs/content/en/api/binders/progress.md index bd3c0c52..276f739b 100644 --- a/docs/content/en/api/binders/progress.md +++ b/docs/content/en/api/binders/progress.md @@ -28,5 +28,5 @@ Returns a binder which binds an object property to a ``progress`` element value. ## Examples ```js -this.bindNode('myKey', '.my-progres', Matreshka.binders.progress()); +this.bindNode('myKey', '.my-progress', Matreshka.binders.progress()); ``` diff --git a/docs/content/en/api/binders/style.md b/docs/content/en/api/binders/style.md index f965ecc0..8a86ef39 100644 --- a/docs/content/en/api/binders/style.md +++ b/docs/content/en/api/binders/style.md @@ -37,9 +37,9 @@ The property value can be transformed using ``mappingFn`` argument. ## Examples ```js -this.bindNode('myKey', '.my-progres', - Matreshka.binders.style('backgroundColor')); -this.myKey = 'red'; // background-color of .my-progress is red now +this.bindNode('myKey', '.my-element', + Matreshka.binders.style('backgroundColor')); +this.myKey = 'red'; // background-color of .my-element is red now ``` **The usage of mapping function** diff --git a/docs/content/en/api/matreshka-array/item-renderer.md b/docs/content/en/api/matreshka-array/item-renderer.md index 828181fa..2c8d0958 100644 --- a/docs/content/en/api/matreshka-array/item-renderer.md +++ b/docs/content/en/api/matreshka-array/item-renderer.md @@ -277,7 +277,7 @@ But sometimes you just need to move an object across few collections without re- ```js this.push_(item, { moveSandbox: true -}) +}); ``` #### ``itemRenderer`` reassigning diff --git a/docs/content/en/api/matreshka-array/method.md b/docs/content/en/api/matreshka-array/method.md index f91b7dd8..9c742a10 100644 --- a/docs/content/en/api/matreshka-array/method.md +++ b/docs/content/en/api/matreshka-array/method.md @@ -49,7 +49,7 @@ Yet they work the same as the ``Array.prototype``. There are only a few remarks: - The methods which should return a new array (``splice``, ``slice``, ``filter``, ``map``...) return a new ``Matreshka.Array`` instance. - The ``keys``, ``values`` and ``entries`` methods return an array instead of an iterator. - ``fill`` reports the replaced items as ``removed`` in the ``remove`` and ``modify`` events, and ``added`` holds one item per filled index. -- ``fill`` and ``copyWithin`` throw an error on a rendered array (one that has a ``container`` or a ``sandbox``). +- ``fill`` and ``copyWithin`` throw an error on a rendered array (one that has a ``container`` or a ``sandbox``) and leave it unchanged. Moreover, the methods generate events which are fired on any array modification. For more detailed information look at [`Matreshka.Array`](/api/matreshka-array/). diff --git a/docs/content/en/api/matreshka-array/on-item-render.md b/docs/content/en/api/matreshka-array/on-item-render.md index bfbc3aac..efd7354f 100644 --- a/docs/content/en/api/matreshka-array/on-item-render.md +++ b/docs/content/en/api/matreshka-array/on-item-render.md @@ -23,7 +23,7 @@ At the same time ``onRender`` method is called in a rendered item with the only | Name | Type | Details | | --- | --- | --- | -| `item` | [`object`](/api/types/object/) | A rendered node of a collection | +| `item` | [`object`](/api/types/object/) | A rendered item of a collection | | `renderEvent` | [`object`](/api/types/object/) | ``"render"`` event object | ## Examples @@ -44,7 +44,10 @@ class MyArray extends Matreshka.Array { return MyModel; } itemRenderer() { - return '
  • ' + return ``; } constructor() { super().bindNode('sandbox', '.my-form'); diff --git a/docs/content/en/api/matreshka-array/restore.md b/docs/content/en/api/matreshka-array/restore.md index 558543e2..34a403aa 100644 --- a/docs/content/en/api/matreshka-array/restore.md +++ b/docs/content/en/api/matreshka-array/restore.md @@ -41,6 +41,9 @@ class MyModel extends Matreshka.Object { } class MyCollection extends Matreshka.Array { + get Model() { + return MyModel; + } get itemRenderer() { return ':sandbox .renderer'; } diff --git a/docs/content/en/api/matreshka-array/to-json.md b/docs/content/en/api/matreshka-array/to-json.md index 7eba6e36..01002c34 100644 --- a/docs/content/en/api/matreshka-array/to-json.md +++ b/docs/content/en/api/matreshka-array/to-json.md @@ -28,7 +28,7 @@ The method works recursively, calling ``toJSON`` for all items which have such m ## Examples ```js -const matreshkaArray = new Matreshka.Array(1, 2, new MatreshkaArray(3, 4)); +const matreshkaArray = new Matreshka.Array(1, 2, new Matreshka.Array(3, 4)); // returns [1, 2, [3, 4]] console.log(matreshkaArray.toJSON()); diff --git a/docs/content/en/api/matreshka-array/track-by.md b/docs/content/en/api/matreshka-array/track-by.md index 27a9598d..6942015d 100644 --- a/docs/content/en/api/matreshka-array/track-by.md +++ b/docs/content/en/api/matreshka-array/track-by.md @@ -44,7 +44,7 @@ arr.recreate([ // the next recreate call // removes an object with _id: 0 // adds an object with _id: 3 -// updates an object with _id: 3 (changes name from Bar to BarNew) +// updates an object with _id: 1 (changes name from Bar to BarNew) // updates an object with _id: 2 (changes name from Baz to BazNew) // resorts collection arr.recreate([ diff --git a/docs/content/en/api/matreshka-static/class.md b/docs/content/en/api/matreshka-static/class.md index a157d694..a7686d55 100644 --- a/docs/content/en/api/matreshka-static/class.md +++ b/docs/content/en/api/matreshka-static/class.md @@ -60,7 +60,7 @@ const C = Matreshka.Class({ const D = Matreshka.Class({ // D is inherited from C extends: C, - method3() { + method3(a, b) { // calling the parent method C.prototype.method3.apply(this, arguments); } diff --git a/docs/content/en/api/matreshka/bind-node.md b/docs/content/en/api/matreshka/bind-node.md index ba1ec664..5b5ba881 100644 --- a/docs/content/en/api/matreshka/bind-node.md +++ b/docs/content/en/api/matreshka/bind-node.md @@ -120,7 +120,7 @@ const checkboxBinder = () => { // to the beginning of the Matreshka.defaultBinders array Matreshka.defaultBinders.unshift(node => { // check if the element is a checkbox - if(node.tagName == 'INPUT' && node.type == 'checkbox') { + if(node.tagName === 'INPUT' && node.type === 'checkbox') { // if checking is OK, return a new binder return checkboxBinder(); } diff --git a/docs/content/en/api/matreshka/instantiate.md b/docs/content/en/api/matreshka/instantiate.md index e8ab3cef..fd73ec91 100644 --- a/docs/content/en/api/matreshka/instantiate.md +++ b/docs/content/en/api/matreshka/instantiate.md @@ -123,7 +123,7 @@ class AppData extends Matreshka.Object { super(data) .instantiate({ friends: Friends, - settins: Settings + settings: Settings }); } } diff --git a/docs/content/en/api/matreshka/trigger.md b/docs/content/en/api/matreshka/trigger.md index a630e5f9..0b3b0103 100644 --- a/docs/content/en/api/matreshka/trigger.md +++ b/docs/content/en/api/matreshka/trigger.md @@ -10,7 +10,7 @@ importance: 1 **Method** ```js -Matreshka#trigger(names, [arg]) → matreshka +Matreshka#trigger(names, [arg]) → object ``` Fires an event @@ -19,7 +19,7 @@ After adding event handlers using [`Matreshka#on`](/api/matreshka/on/), [`Matres > Note that the method has [static alternative](/api/matreshka-static/trigger/) -**Returns:** [`matreshka`](/api/types/matreshka/) – self +**Returns:** [`object`](/api/types/object/) – self ## Arguments diff --git a/docs/content/en/api/router.md b/docs/content/en/api/router.md index adb4e404..e4a933cf 100644 --- a/docs/content/en/api/router.md +++ b/docs/content/en/api/router.md @@ -41,7 +41,7 @@ By changing these properties you can trigger needed procedures (update the path, #### Methods - ``subscribe(object, route)`` - subscribes object for route changes. -- ``init()`` - used for lazy initialization in ``subscribe`` method (no need to call it manually). +- ``init()`` - used for lazy initialization on first use of ``Matreshka.Router.hash`` or ``Matreshka.Router.history`` and in the ``subscribe`` method (no need to call it manually). ## Arguments diff --git a/docs/content/en/api/types/matreshka.md b/docs/content/en/api/types/matreshka.md index 4ab8c4ce..c2b078d8 100644 --- a/docs/content/en/api/types/matreshka.md +++ b/docs/content/en/api/types/matreshka.md @@ -15,5 +15,5 @@ importance: 2 ```js const matreshka = new Matreshka(); -obj.calc('a', 'b'); +matreshka.calc('a', 'b'); ``` diff --git a/docs/content/en/faq.md b/docs/content/en/faq.md index 6dffb45a..2850239d 100644 --- a/docs/content/en/faq.md +++ b/docs/content/en/faq.md @@ -72,13 +72,13 @@ class User extends Matreshka.Object { // pseudo-sandbox creation for syntax sugar at selectors // (not required) nodeA: node, - name: ':bound(nodeA) .name', - email: ':bound(nodeA) .email', + name: { node: ':bound(nodeA) .name', binder: Matreshka.binders.text() }, + email: { node: ':bound(nodeA) .email', binder: Matreshka.binders.text() }, }); } else if(parentArray instanceof UsersB) { this.bindNode({ nodeB: node, - name: ':bound(nodeB) .user-name', + name: { node: ':bound(nodeB) .user-name', binder: Matreshka.binders.text() }, }); } } diff --git a/docs/content/ru/api/binders/class-name.md b/docs/content/ru/api/binders/class-name.md index 5ee7648f..5de40e84 100644 --- a/docs/content/ru/api/binders/class-name.md +++ b/docs/content/ru/api/binders/class-name.md @@ -36,18 +36,18 @@ Matreshka.binders.className(className, [bool=true]) → binder ```js this.bindNode('myKey', '.my-element', - Matreshka.binders.className('blah')); + Matreshka.binders.className('foo')); -this.myKey = true; // добавляет класс 'blah' +this.myKey = true; // добавляет класс 'foo' -this.myKey = false; // убирает класс 'blah' +this.myKey = false; // убирает класс 'foo' ``` ```js this.bindNode('myKey', '.my-element', - Matreshka.binders.className('blah', false)); + Matreshka.binders.className('foo', false)); -this.myKey = false; // добавляет класс 'blah' +this.myKey = false; // добавляет класс 'foo' -this.myKey = true; // убирает класс 'blah' +this.myKey = true; // убирает класс 'foo' ``` diff --git a/docs/content/ru/api/matreshka-array.md b/docs/content/ru/api/matreshka-array.md index 12a4938a..d27e7ccf 100644 --- a/docs/content/ru/api/matreshka-array.md +++ b/docs/content/ru/api/matreshka-array.md @@ -137,7 +137,7 @@ new Matreshka.Array(42); **Передача элементов при создании** ```js -new Matreshka.Array('Hi', { a: 'b' }); +new Matreshka.Array('Hi', {a: 'b'}); ``` **Наследование** diff --git a/docs/content/ru/api/matreshka-array/item-renderer.md b/docs/content/ru/api/matreshka-array/item-renderer.md index 63f9f991..0d9dcbbc 100644 --- a/docs/content/ru/api/matreshka-array/item-renderer.md +++ b/docs/content/ru/api/matreshka-array/item-renderer.md @@ -32,9 +32,8 @@ class MyArray extends Matreshka.Array { itemRenderer = '
  • '; get Model() { return MyModel; } constructor() { - super(); // определяем песочницу - this.bindNode('sandbox', '.my-list'); + super().bindNode('sandbox', '.my-list'); } } ``` @@ -197,7 +196,7 @@ const app = new MyArray(); Не забывайте, что в Matreshka.js реализована возможность отлова делегированных событий. Т. е. сам массив может отловить событие рендеринга элемента, используя имя события ``*@render`` (см. документацию к [`eventNames`](/ru/api/types/event-names/)). ```js this.on('*@render', () => { - alert('Child element is rendered'); + alert('Child item is rendered'); }); ``` @@ -275,7 +274,7 @@ this.push_(item, { #### Переопределение ``itemRenderer`` При переустановке свойства ``itemRenderer``, коллекция автоматически перерисовывается. ```js -this.itemRenderer = '
    '; +this.itemRenderer = '
    '; ``` Эта возможность полезна в том случае, когда разработчик желает загрузить шаблон с сервера. ```js diff --git a/docs/content/ru/api/matreshka-array/method.md b/docs/content/ru/api/matreshka-array/method.md index e1f6b24a..f9f61306 100644 --- a/docs/content/ru/api/matreshka-array/method.md +++ b/docs/content/ru/api/matreshka-array/method.md @@ -49,7 +49,7 @@ Matreshka.Array#METHOD() - Методы, которые в оригинальном виде возвращают новый массив (``splice``, ``slice``, ``filter``, ``map``...), возвращают новый экземпляр ``Matreshka.Array``. - Методы ``keys``, ``values`` и ``entries`` возвращают массив вместо итератора. - ``fill`` передаёт заменённые элементы как ``removed`` в события ``remove`` и ``modify``, а ``added`` содержит по одному элементу на каждый заполненный индекс. -- ``fill`` и ``copyWithin`` бросают ошибку на отрендеренном массиве (у которого есть ``container`` или ``sandbox``). +- ``fill`` и ``copyWithin`` бросают ошибку на отрендеренном массиве (у которого есть ``container`` или ``sandbox``) и не меняют его. Кроме всего, методы генерируют события связанные с любой модификацией массива. Подробнее см. [`Matreshka.Array`](/ru/api/matreshka-array/). diff --git a/docs/content/ru/api/matreshka-array/on-item-render.md b/docs/content/ru/api/matreshka-array/on-item-render.md index e5a81fc7..19463a59 100644 --- a/docs/content/ru/api/matreshka-array/on-item-render.md +++ b/docs/content/ru/api/matreshka-array/on-item-render.md @@ -23,7 +23,7 @@ Matreshka.Array#onItemRender(item, renderEvent) | Имя | Тип | Описание | | --- | --- | --- | -| `item` | [`object`](/ru/api/types/object/) | Отрисованный элемент коллекции | +| `item` | [`object`](/ru/api/types/object/) | Отрисованный объект коллекции | | `renderEvent` | [`object`](/ru/api/types/object/) | Объект события ``render`` | ## Примеры @@ -44,7 +44,10 @@ class MyArray extends Matreshka.Array { return MyModel; } itemRenderer() { - return '
  • ' + return ``; } constructor() { super().bindNode('sandbox', '.my-form'); diff --git a/docs/content/ru/api/matreshka-array/restore.md b/docs/content/ru/api/matreshka-array/restore.md index 59553e22..ec59f212 100644 --- a/docs/content/ru/api/matreshka-array/restore.md +++ b/docs/content/ru/api/matreshka-array/restore.md @@ -41,6 +41,9 @@ class MyModel extends Matreshka.Object { } class MyCollection extends Matreshka.Array { + get Model() { + return MyModel; + } get itemRenderer() { return ':sandbox .renderer'; } diff --git a/docs/content/ru/api/matreshka-array/to-json.md b/docs/content/ru/api/matreshka-array/to-json.md index f01f2de7..24d29396 100644 --- a/docs/content/ru/api/matreshka-array/to-json.md +++ b/docs/content/ru/api/matreshka-array/to-json.md @@ -28,7 +28,7 @@ Matreshka.Array#toJSON([recursive=true]) → array ## Примеры ```js -const matreshkaArray = new Matreshka.Array(1, 2, new MatreshkaArray(3, 4)); +const matreshkaArray = new Matreshka.Array(1, 2, new Matreshka.Array(3, 4)); // возвращает [1, 2, [3, 4]] console.log(matreshkaArray.toJSON()); diff --git a/docs/content/ru/api/matreshka-object.md b/docs/content/ru/api/matreshka-object.md index eda2e164..4364cd95 100644 --- a/docs/content/ru/api/matreshka-object.md +++ b/docs/content/ru/api/matreshka-object.md @@ -49,7 +49,7 @@ this.toJSON(); При добавлении и изменении свойств, отвечающих за данные генерируется события ``set`` и ``modify``, при удалении - ``remove`` и ``modify``. Т. е. любые изменения данных можно слушать с помощью ``modify``. ```js this.on('modify', () => { - alert('Object is modified'); + alert('an object is modified'); }); ``` diff --git a/docs/content/ru/api/matreshka-object/each.md b/docs/content/ru/api/matreshka-object/each.md index 6f2b0d6c..70653026 100644 --- a/docs/content/ru/api/matreshka-object/each.md +++ b/docs/content/ru/api/matreshka-object/each.md @@ -34,7 +34,7 @@ this .addDataKeys('c') .each((value, key) => { console.log(key, value); - }); + }, this); ; // >>> a 1, b 2, c undefined ``` diff --git a/docs/content/ru/api/matreshka-static/parse-bindings.md b/docs/content/ru/api/matreshka-static/parse-bindings.md index 757f8a38..91cd8088 100644 --- a/docs/content/ru/api/matreshka-static/parse-bindings.md +++ b/docs/content/ru/api/matreshka-static/parse-bindings.md @@ -33,8 +33,8 @@ Matreshka.parseBindings() → $nodes ```js const object = {}; -const $node = Matreshka.parseBindings(object, `

    - Hello, {{name}} -

    `); +const $node = Matreshka.parseBindings(object, ` +

    Hello, {{name}}

    +`); object.name = 'World'; ``` diff --git a/docs/content/ru/api/matreshka/bind-node.md b/docs/content/ru/api/matreshka/bind-node.md index aab27dbc..4cce143f 100644 --- a/docs/content/ru/api/matreshka/bind-node.md +++ b/docs/content/ru/api/matreshka/bind-node.md @@ -148,7 +148,8 @@ const uiSlider = (min, max) => { } } }; - +``` +```js this.bindNode('myKey1', '.my-slider1', uiSlider(0, 100)); this.bindNode('myKey2', '.my-slider2', uiSlider(1, 1000)); this.myKey1 = 42; @@ -309,7 +310,7 @@ setTimeout(() => { Например, мы хотим связать ``input[type="text"]`` со свойством. По умолчанию, стандартный байндер для этого элемента содержит свойство ``"on"`` со значением ``"input"``. Это значит, что значение свойства экземпляра и состояние элемента будут синхронизированы сразу после ввода или удаления символа пользователем. В случае, если вы хотите, чтоб синхронизация происходила по DOM событию ``"blur"``, вам потребуется передать третьим аргументом объект, содержащий единственное свойство ``"on"``. Этот объект объединится со стандартным байндером, сохранив при этом значения ``getValue`` и ``setValue``. ```js -this.bindNode('x', '.my-input', { on: "blur" }); +this.bindNode('myKey', '.my-input', { on: 'blur' }); ``` Для отмены этого поведения и использования байндера как он есть, в объект события можно передать флаг ``useExactBinder`` со значением ``true``. @@ -317,7 +318,7 @@ this.bindNode('x', '.my-input', { on: "blur" }); ```js this.bindNode('x', node, binder, { useExactBinder: true -}) +}); ``` **Возвращает:** [`object`](/ru/api/types/object/) – self diff --git a/docs/content/ru/api/matreshka/instantiate.md b/docs/content/ru/api/matreshka/instantiate.md index 471e6680..7f4f1742 100644 --- a/docs/content/ru/api/matreshka/instantiate.md +++ b/docs/content/ru/api/matreshka/instantiate.md @@ -92,7 +92,7 @@ class MyParentClass extends Matreshka { **Нестандартный способ использования ``updateCallback`` для игнорирования любых изменений свойства.** ```js -this.instantiate('x', SubClass, () => {}); +this.instantiate('x', MyClass, () => {}); ``` **В случае, если ваш класс не подерживает использование оператора ``new``, вместо ``instantiate`` воспользуйтесь методом [`Matreshka#mediate`](/ru/api/matreshka/mediate/).** diff --git a/docs/content/ru/api/matreshka/mediate.md b/docs/content/ru/api/matreshka/mediate.md index 764e196a..66480013 100644 --- a/docs/content/ru/api/matreshka/mediate.md +++ b/docs/content/ru/api/matreshka/mediate.md @@ -10,7 +10,7 @@ importance: 2 **Метод** ```js -Matreshka#mediate(key, mediator) +Matreshka#mediate(key, mediator) → object ``` Трансформирует значение свойства при его изменении @@ -25,6 +25,8 @@ mediate(object, key, mediator); // вместо this.mediate(key, mediator); ``` +**Возвращает:** [`object`](/ru/api/types/object/) – self + ## Аргументы | Имя | Тип | Описание | diff --git a/docs/content/ru/api/matreshka/off.md b/docs/content/ru/api/matreshka/off.md index 7894a9f6..5bbdac13 100644 --- a/docs/content/ru/api/matreshka/off.md +++ b/docs/content/ru/api/matreshka/off.md @@ -10,7 +10,7 @@ importance: 1 **Метод** ```js -Matreshka#off([names], [callback], [context]) → matreshka +Matreshka#off([names], [callback], [context]) → object ``` Удаляет обработчик события @@ -19,7 +19,7 @@ Matreshka#off([names], [callback], [context]) → matreshka Без аргументов ``off`` удаляет все обработчики, добавленные через ``on``, ``once`` и ``onDebounce``, и генерирует ``removeevent`` для каждого из них. Привязки, зависимости ``calc`` и внутренние события ``Matreshka.Object`` и ``Matreshka.Array`` остаются. -**Возвращает:** [`matreshka`](/ru/api/types/matreshka/) – self +**Возвращает:** [`object`](/ru/api/types/object/) – self **Генерирует события:** `removeevent`, `removeevent:NAME` diff --git a/docs/content/ru/api/matreshka/parse-bindings.md b/docs/content/ru/api/matreshka/parse-bindings.md index d8b86f38..02d69f45 100644 --- a/docs/content/ru/api/matreshka/parse-bindings.md +++ b/docs/content/ru/api/matreshka/parse-bindings.md @@ -38,15 +38,11 @@ JS: this.user = {name: 'Joe'} JS: this.x = 'some value'; --> -``` -```html -``` -```html -``` -```html -``` -```html /g, ''), /\/\*[\s\S]*?\*\//g, '') + .replace(/(^|[\s;{}(),])\/\/.*$/gm, '$1') + .split('\n') + .map((line) => line.trim().replace(/[ \t]+/g, ' ')) + .filter(Boolean) + })); + +// Strings and HTML text, the parts a translation may change. +const maskText = (line) => line.replace(/(['"`])(?:\\.|(?!\1).)*\1/g, '$1$1').replace(/>[^<>]*<'); +const CYRILLIC = /\p{Script=Cyrillic}/u; + +describe('docs code blocks', () => { + // A translated page shows the same code as the English one; only comments and text in strings are translated. + it('are the same code in every language', () => { + const english = new Map(pages.filter(({ lang }) => lang === 'en').map((page) => [page.name, codeBlocks(page.md)])); + for (const { file, name, lang, md } of pages) { + if (lang === 'en') continue; + const blocks = codeBlocks(md); + const expected = english.get(name); + expect( + blocks.map((block) => block.lang), + `${file}: code blocks` + ).toEqual(expected.map((block) => block.lang)); + blocks.forEach((block, i) => { + expect(block.lines.length, `${file}, block ${i + 1}: lines`).toBe(expected[i].lines.length); + block.lines.forEach((line, j) => { + const en = expected[i].lines[j]; + const translated = line !== en && CYRILLIC.test(line) && maskText(line) === maskText(en); + if (!translated) expect(line, `${file}, block ${i + 1}, line ${j + 1}`).toBe(en); + }); + }); + } + }); +}); diff --git a/scripts/check-legacy-run.mjs b/scripts/check-legacy-run.mjs index c4167118..0a632e80 100644 --- a/scripts/check-legacy-run.mjs +++ b/scripts/check-legacy-run.mjs @@ -42,6 +42,14 @@ function toIE11(window) { 'getOwnPropertySymbols', 'getOwnPropertyDescriptors' ]); + // ES5's Object.keys: a primitive throws + const { keys } = window.Object; + window.Object.keys = (object) => { + if (object === null || (typeof object !== 'object' && typeof object !== 'function')) { + throw new window.TypeError('Object.keys: argument is not an Object'); + } + return keys(object); + }; drop(window.Array, ['from', 'of']); drop(window.Array.prototype, [ 'includes', diff --git a/skills/matreshka/SKILL.md b/skills/matreshka/SKILL.md index aadd650c..90c9070b 100644 --- a/skills/matreshka/SKILL.md +++ b/skills/matreshka/SKILL.md @@ -47,7 +47,7 @@ It weighs about 73 kB minified, 24 kB gzipped, 21 kB with brotli (what a CDN sen ### B. CDN ES module, no bundler -`matreshka.min.mjs` is the whole library as one minified ES module: every named export of `matreshka` plus `initRouter`, `Router` and `parseForm`, and the default export is `Matreshka` with them attached. One request, the same size as the script-tag file. +`matreshka.min.mjs` is the whole library as one minified ES module: every named export of `matreshka` plus `initRouter`, `Router` and `parseForm`, and the default export is `Matreshka` with them attached. One request, a little smaller than the script-tag file: it is not compiled down to ES5. ```html + `; + class MyModel extends Matreshka.Object { + constructor(data) { + super(data); + this.addDataKeys('value'); + } + onRender() { + this.bindNode('value', ':sandbox', Matreshka.binders.html()); + } + } + class MyCollection extends Matreshka.Array { + get Model() { + return MyModel; + } + get itemRenderer() { + return ':sandbox .renderer'; + } + constructor() { + super().bindNode('sandbox', '.collection-node').restore(':sandbox li'); + } + } + const myCollection = new MyCollection(); + myCollection.push({ value: 'Four' }); + await wait(); + expect(myCollection.toJSON()).toEqual([{ value: 'One' }, { value: 'Two' }, { value: 'Three' }, { value: 'Four' }]); + expect($('.collection-node').querySelectorAll('li').length).toBe(4); + }); + + it('DOC-4 instantiate.md: new appData keeps the structure, settings included', () => { + class Credentials extends Matreshka.Object {} + class Settings extends Matreshka.Object { + constructor(data) { + super(data).instantiate('credentials', Credentials); + } + } + class Friend extends Matreshka.Object {} + class Friends extends Matreshka.Array { + get Model() { + return Friend; + } + trackBy = 'id'; + constructor(data) { + super(...data); + } + } + class AppData extends Matreshka.Object { + constructor(data) { + super(data).instantiate({ + friends: Friends, + settings: Settings + }); + } + } + class App extends Matreshka { + constructor(appData) { + super(); + this.appData = appData; + this.instantiate('appData', AppData); + } + } + const app = new App({ + settings: { name: 'Vasiliy Vasiliev', credentials: { email: 'vasia.vasia@gmail.com' } }, + friends: [ + { name: 'Yulia Zuyeva', id: 1 }, + { name: 'Konstantin Konstantinopolsky', id: 2 }, + { name: 'nagibator3000', id: 3 } + ] + }); + const { settings } = app.appData; + const { credentials } = settings; + const [yulia] = app.appData.friends; + expect(settings).toBeInstanceOf(Settings); + expect(credentials).toBeInstanceOf(Credentials); + + app.appData = { + settings: { name: 'Petr Petrov', credentials: { email: 'petr.petrov@gmail.com' } }, + friends: [ + { name: 'Yulechka Zuyeva', id: 1 }, + { name: 'Konstantin Konstantinopolsky', id: 2 } + ] + }; + expect(app.appData.settings).toBe(settings); + expect(app.appData.settings.credentials).toBe(credentials); + expect(settings.name).toBe('Petr Petrov'); + expect(credentials.email).toBe('petr.petrov@gmail.com'); + expect(app.appData.friends[0]).toBe(yulia); + expect(app.appData.friends.length).toBe(2); + }); + + it('DOC-5 on-item-render.md: onRender binds the checkbox and the text of the rendered item', async () => { + document.body.innerHTML = '
    '; + const items = []; + class MyModel extends Matreshka.Object { + onRender() { + this.bindNode('isChecked', ':sandbox .my-checkbox'); + this.bindNode('text', ':sandbox .text', Matreshka.binders.html()); + } + } + class MyArray extends Matreshka.Array { + get Model() { + return MyModel; + } + itemRenderer() { + return ``; + } + constructor() { + super().bindNode('sandbox', '.my-form'); + } + onItemRender(item) { + items.push(item); + } + } + const app = new MyArray(); + app.push({ isChecked: true, text: 'Buy a raccoon' }); + await wait(); + expect($('.my-checkbox').checked).toBe(true); + expect($('.text').textContent).toBe('Buy a raccoon'); + // the first argument is the item, not a node + expect(items).toEqual([app[0]]); + expect(items[0]).toBeInstanceOf(MyModel); + }); + + it('DOC-6 track-by.md: the object with _id 1 is reused as BarNew, _id 3 is new', () => { + class MyArray extends Matreshka.Array { + get trackBy() { + return '_id'; + } + } + const arr = new MyArray(); + arr.recreate([ + { _id: 0, name: 'Foo' }, + { _id: 1, name: 'Bar' }, + { _id: 2, name: 'Baz' } + ]); + const before = Array.from(arr); + arr.recreate([ + { _id: 1, name: 'BarNew' }, + { _id: 3, name: 'Qux' }, + { _id: 2, name: 'BazNew' } + ]); + const byId = (id) => arr.find((item) => item._id === id); + expect(byId(1)).toBe(before[1]); + expect(byId(1).name).toBe('BarNew'); + expect(byId(2)).toBe(before[2]); + expect(byId(2).name).toBe('BazNew'); + expect(before).not.toContain(byId(3)); + expect(Array.from(arr)).not.toContain(before[0]); + expect(arr.map((item) => item._id).toJSON()).toEqual([1, 3, 2]); + }); + + it('DOC-7 progress.md and style.md: the selectors name the bound node', async () => { + document.body.innerHTML = '
    '; + const object = new Matreshka(); + object.bindNode('myKey', '.my-progress', Matreshka.binders.progress()); + object.bindNode('myStyle', '.my-element', Matreshka.binders.style('backgroundColor')); + object.myKey = 42; + object.myStyle = 'red'; + await wait(); + expect($('.my-progress').value).toBe(42); + // background-color of .my-element is red now + expect($('.my-element').style.backgroundColor).toBe('red'); + }); + + it('DOC-8 types/matreshka.md: the sample calls calc on the instance it creates', () => { + const matreshka = new Matreshka(); + matreshka.b = 1; + expect(matreshka.calc('a', 'b')).toBe(matreshka); + expect(matreshka.a).toBe(1); + }); + + it('DOC-9 to-json.md: a nested Matreshka.Array', () => { + const matreshkaArray = new Matreshka.Array(1, 2, new Matreshka.Array(3, 4)); + // returns [1, 2, [3, 4]] + expect(matreshkaArray.toJSON()).toEqual([1, 2, [3, 4]]); + // returns [1, 2, MatreshkaArray] + expect(matreshkaArray.toJSON(false)[2].isMatreshkaArray).toBe(true); + }); +}); diff --git a/test/spec/regressions/sweep5_es5_spec.js b/test/spec/regressions/sweep5_es5_spec.js new file mode 100644 index 00000000..8b7404b9 --- /dev/null +++ b/test/spec/regressions/sweep5_es5_spec.js @@ -0,0 +1,245 @@ +import Class from 'src/class'; +import MatreshkaArray from 'src/array'; +import MatreshkaObject from 'src/object'; +import arrayFrom from 'src/_helpers/arrayfrom'; +import assign from 'src/_helpers/assign'; +import forOwn from 'src/_helpers/forown'; +import fromEntries from 'src/_helpers/fromentries'; +import bindNode from 'src/bindnode'; +import instantiate from 'src/instantiate'; +import parseBindings from 'src/parsebindings'; +import { html, output, text } from 'src/binders'; +import { parseHTML } from 'src/_dom'; + +// sweep 5, the ES5 build: each case also runs against dist/ (npm run test:dist), where Babel compiled the code for IE 11 +const without = (object, names, fn) => { + const saved = names.map((name) => Object.getOwnPropertyDescriptor(object, name)); + for (const name of names) { + Object.defineProperty(object, name, { value: undefined, configurable: true, writable: true }); + } + try { + return fn(); + } finally { + names.forEach((name, i) => { + if (saved[i]) Object.defineProperty(object, name, saved[i]); + else delete object[name]; + }); + } +}; + +const withoutTemplate = (fn) => { + const { createElement } = document; + document.createElement = function (name) { + return createElement.call(this, name === 'template' ? 'x-template' : name); + }; + try { + return fn(); + } finally { + document.createElement = createElement; + } +}; + +// IE 11's Object.keys is ES5's: a primitive throws +const withES5Keys = (fn) => { + const { keys } = Object; + Object.keys = (object) => { + if (object === null || (typeof object !== 'object' && typeof object !== 'function')) { + throw new TypeError('Object.keys: argument is not an Object'); + } + return keys(object); + }; + try { + return fn(); + } finally { + Object.keys = keys; + } +}; + +const names = (nodes) => nodes.map((node) => node.nodeName); + +describe('sweep 5 ES5: parseHTML without