From 1d6352092b110c5f6f87aa9e582ac38b898b7dc6 Mon Sep 17 00:00:00 2001 From: Jelle De Loecker Date: Sun, 28 Apr 2024 13:29:53 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20variable=20acce?= =?UTF-8?q?ss=20in=20compiled=20template=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Previously, template variables were accessed directly using property access (e.g., `{{ my_value }}` was converted to something like `renderer.print(vars.my_value)`). - This approach proved to be difficult to maintain, so it has been refactored to use function-based access instead. - Now, template variables are accessed using a `get()` method (e.g., `renderer.print(vars.get('my_value'))`). - This change should improve the overall maintainability and flexibility of the templating engine. --- CHANGELOG.md | 1 + lib/core/renderer.js | 66 ++++---- lib/core/variables.js | 206 ++++++++++++++++------- lib/element/custom_element.js | 48 +++--- lib/expression/each.js | 33 ++-- lib/expression/expression.js | 18 +- lib/expression/set.js | 4 +- lib/expression/with.js | 30 ++-- test/02-variables.js | 40 +++++ test/05-custom_elements.js | 6 + test/helpers/with_reactive_attributes.js | 11 ++ 11 files changed, 299 insertions(+), 164 deletions(-) create mode 100644 test/helpers/with_reactive_attributes.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c823ded..dd07cf92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Add `Variables#get(key)` & `Variables#setFromTemplate(key, value)` method * Don't let `Hawkejs.doNextSync(promise)` swallow errors * Use methods for getting (and proxy traps for setting) variables on the `Variables` class +* Refactor variable access in compiled template code ## 2.3.19 (2024-04-13) diff --git a/lib/core/renderer.js b/lib/core/renderer.js index 9312f4f5..b4e171e1 100644 --- a/lib/core/renderer.js +++ b/lib/core/renderer.js @@ -519,15 +519,15 @@ Renderer.setProperty(function theme() { * * @author Jelle De Loecker * @since 2.2.15 - * @version 2.2.15 + * @version 2.4.0 * * @type {HTMLElement} */ Renderer.setProperty(function current_element() { if (this.on_template_root_level) { - if (this.current_variables.$ancestor_element) { - return this.current_variables.$ancestor_element; + if (this.current_variables.has('$ancestor_element')) { + return this.current_variables.get('$ancestor_element'); } return; @@ -2247,7 +2247,7 @@ Renderer.setMethod(function applyElementOptions(element, options, for_sync_rende for (i = 0; i < options.variables.length; i++) { val = options.variables[i]; - element[Hawkejs.VARIABLES][val.name] = val.value; + element[Hawkejs.VARIABLES].set(val.name, val.value); } } @@ -2675,11 +2675,11 @@ Renderer.setMethod(function hasBeenRegistered(element) { let variables = this.root_renderer.variables; - if (!variables || !variables.__he_elements) { + if (!variables || !variables.has('__he_elements')) { return false; } - return variables.__he_elements.indexOf(element) > -1; + return variables.get('__he_elements').indexOf(element) > -1; }); /** @@ -2687,17 +2687,21 @@ Renderer.setMethod(function hasBeenRegistered(element) { * * @author Jelle De Loecker * @since 2.0.0 - * @version 2.1.3 + * @version 2.4.0 * * @param {Element} element */ Renderer.setMethod(function registerElementInstance(element) { - var variables = this.root_renderer.variables, + let he_elements, + variables = this.root_renderer.variables, id = element.hawkejs_id || element.dataset.hid; - if (!variables.__he_elements) { - variables.__he_elements = []; + if (!variables.has('__he_elements')) { + he_elements = []; + variables.set('__he_elements', he_elements); + } else { + he_elements = variables.get('__he_elements'); } if (!id) { @@ -2706,12 +2710,12 @@ Renderer.setMethod(function registerElementInstance(element) { // Set the id element.setIdentifier(id); } else { - if (variables.__he_elements.indexOf(element) > -1) { + if (he_elements.indexOf(element) > -1) { return; } } - variables.__he_elements.push(element); + he_elements.push(element); }); /** @@ -3041,7 +3045,7 @@ Renderer.setCommand(function bottom() { * * @author Jelle De Loecker * @since 1.0.0 - * @version 2.3.16 + * @version 2.4.0 * * @param {Array|String} names The partial to render * @param {Object} options @@ -3078,7 +3082,7 @@ Renderer.setMethod(function addSubtemplate(names, options, variables) { // The Templates render method expects fully prepared variables. variables = this.prepareVariables(variables); - variables.$ancestor_element = ancestor_element; + variables.setFromTemplate('$ancestor_element', ancestor_element); templates.render(variables).done(function done(err, block) { @@ -3651,7 +3655,7 @@ Renderer.setMethod(function createOpenElement(name, add_identifier) { * * @author Jelle De Loecker * @since 2.2.3 - * @version 2.3.11 + * @version 2.4.0 * * @param {HTMLElement} element */ @@ -3662,7 +3666,7 @@ Renderer.setMethod(function addFallbackParentElementGetter(element) { } // See if an ancestor element has been defined - let $ancestor_element = this.current_variables.$ancestor_element; + let $ancestor_element = this.current_variables.get('$ancestor_element'); if (!$ancestor_element) { return; @@ -3992,7 +3996,7 @@ Renderer.setMethod(function add_link(href, options) { * * @author Jelle De Loecker * @since 1.0.0 - * @version 2.2.16 + * @version 2.4.0 * * @param {String} name * @param {Mixed} value @@ -4001,19 +4005,19 @@ Renderer.setCommand(function set(name, value) { if (arguments.length == 1) { - if (this.current_template && this.current_template.variables && typeof this.current_template.variables[name] != 'undefined') { - return this.current_template.variables[name]; + if (this.current_template && this.current_template.variables && this.current_template.variables.has(name)) { + return this.current_template.variables.get(name); } // If there is a parent renderer, look there if this does not have it - if (!this.is_root_renderer && typeof this.variables[name] == 'undefined') { - return this.root_renderer.set(name); + if (!this.is_root_renderer && this.variables.has(name)) { + return this.root_renderer.get(name); } - return this.variables[name]; + return this.variables.get(name); } - this.variables[name] = value; + this.variables.set(name, value); }); /** @@ -4082,7 +4086,7 @@ Renderer.setCommand(function internal(name, value) { * * @author Jelle De Loecker * @since 1.0.0 - * @version 2.0.0 + * @version 2.4.0 * * @param {String} name * @param {Mixed} value @@ -4092,8 +4096,8 @@ Renderer.setCommand(function expose(name, value) { if (arguments.length == 1) { // Look inside the viewrender first - if (typeof this.expose_to_scene[name] !== 'undefined') { - return this.expose_to_scene[name]; + if (this.expose_to_scene.has(name)) { + return this.expose_to_scene.get(name); } // If the item hasn't been found yet, look in the scene @@ -4108,7 +4112,7 @@ Renderer.setCommand(function expose(name, value) { return; } - this.expose_to_scene[name] = value; + this.expose_to_scene.set(name, value); }); /** @@ -4116,7 +4120,7 @@ Renderer.setCommand(function expose(name, value) { * * @author Jelle De Loecker * @since 1.1.1 - * @version 2.2.16 + * @version 2.4.0 * * @param {String} name * @param {Mixed} value @@ -4126,14 +4130,14 @@ Renderer.setCommand(function serverVar(name, value) { if (arguments.length == 1) { // If there is a parent renderer, look there if this does not have it - if (!this.is_root_renderer && typeof this.server_variables[name] == 'undefined') { + if (!this.is_root_renderer && this.server_variables.has(name)) { return this.root_renderer.serverVar(name); } - return this.server_variables[name]; + return this.server_variables.get(name); } - this.server_variables[name] = value; + this.server_variables.set(name, value); }); /** diff --git a/lib/core/variables.js b/lib/core/variables.js index d53952fd..1ff4be32 100644 --- a/lib/core/variables.js +++ b/lib/core/variables.js @@ -1,7 +1,9 @@ const PARENT = Symbol('parent'), RENDERER = Symbol('renderer'), TRAPLESS = Symbol('trapless'), - PROXY = Symbol('proxy'); + PROXY = Symbol('proxy'), + VALUES = Symbol('values'), + GETTERS = Symbol('getters'); /** * This way we can skip transforming ejs `my_value = 1` @@ -12,6 +14,26 @@ const PARENT = Symbol('parent'), * @version 2.4.0 */ const TRAPS = { + get(target, name) { + + if (typeof name == 'symbol') { + return target[name]; + } + + if (target[VALUES].has(name)) { + return target[VALUES].get(name); + } + + if (target[GETTERS] && target[GETTERS].has(name)) { + return target[GETTERS].get(name)(); + } + + if (target[PARENT] && target[PARENT].has(name)) { + return target[PARENT].get(name); + } + + return target[name]; + }, set(target, name, value) { if (typeof name == 'symbol') { @@ -37,11 +59,36 @@ const Variables = Fn.inherits('Hawkejs.Base', function Variables(renderer, varia this[PARENT] = null; this[RENDERER] = renderer; this[TRAPLESS] = this; + this[VALUES] = mapify(variables); +}); - if (variables != null) { - Object.assign(this, variables); +/** + * Turn the given input into a map + * + * @author Jelle De Loecker + * @since 2.4.0 + * @version 2.4.0 + * + * @param {Object|Map|Variables} input + * + * @return {Map} + */ +function mapify(input) { + + if (!input) { + return new Map(); } -}); + + if (input instanceof Map) { + return new Map(input); + } + + if (input instanceof Variables) { + return mapify(input[TRAPLESS][VALUES]); + } + + return new Map(Object.entries(input)); +} /** * Make sure the result is a valid Variables instance @@ -81,7 +128,7 @@ Variables.setStatic(function cast(value, renderer) { * @version 2.2.13 */ Variables.setProperty(function length() { - return Object.keys(this).length; + return this[VALUES].size; }); /** @@ -102,6 +149,34 @@ Variables.setMethod(function getProxy() { return this[PROXY]; }); +/** + * Is the given key available? + * + * @author Jelle De Loecker + * @since 2.4.0 + * @version 2.4.0 + * + * @param {string} key + * + * @return {boolean} + */ +Variables.setMethod(function has(key) { + + if (this[VALUES].has(key)) { + return true; + } + + if (this[GETTERS] && this[GETTERS].has(key)) { + return true; + } + + if (this[PARENT]) { + return this[PARENT].has(key); + } + + return false; +}); + /** * Get a specific variable * @@ -110,7 +185,50 @@ Variables.setMethod(function getProxy() { * @version 2.4.0 */ Variables.setMethod(function get(key) { - return this[key]; + + if (this[VALUES].has(key)) { + return this[VALUES].get(key); + } + + if (this[GETTERS] && this[GETTERS].has(key)) { + return this[GETTERS].get(key)(); + } + + if (this[PARENT]) { + return this[PARENT].get(key); + } +}); + +/** + * Set a getter. + * These are functions that will be called when the value is requested. + * These values will not survive a clone. + * + * @author Jelle De Loecker + * @since 2.4.0 + * @version 2.4.0 + * + * @param {string} key + * @param {Function} getter + */ +Variables.setMethod(function setEphemeralGetter(key, getter) { + + if (!this[GETTERS]) { + this[GETTERS] = new Map(); + } + + this[GETTERS].set(key, getter); +}); + +/** + * Set a specific variable from a template + * + * @author Jelle De Loecker + * @since 2.4.0 + * @version 2.4.0 + */ +Variables.setMethod(function set(key, value) { + return this[VALUES].set(key, value); }); /** @@ -121,7 +239,7 @@ Variables.setMethod(function get(key) { * @version 2.4.0 */ Variables.setMethod(function setFromTemplate(key, value) { - return this[key] = value; + return this.set(key, value); }); /** @@ -133,17 +251,7 @@ Variables.setMethod(function setFromTemplate(key, value) { * @version 2.4.0 */ Variables.setMethod(function getOwnDict() { - - const keys = Object.keys(this); - - let result = {}, - key; - - for (key of keys) { - result[key] = this[key]; - } - - return result; + return Object.fromEntries(this[VALUES]); }); /** @@ -174,21 +282,6 @@ Variables.setMethod(function getExistingCloneIfValid(clone_name) { } }); -/** - * Create a new shim - * - * @author Jelle De Loecker - * @since 2.0.0 - * @version 2.4.0 - */ -Variables.setMethod(function createShim() { - - let result = Object.create(this[TRAPLESS]); - result[PROXY] = null; - - return result; -}); - /** * Clone the object for Hawkejs * @@ -202,7 +295,7 @@ Variables.setMethod(function createShim() { */ Variables.setMethod(function toHawkejs(wm) { - var result = new Variables(this[RENDERER]); + let result = new Variables(this[RENDERER]); if (!wm) { wm = new WeakMap(); @@ -219,7 +312,7 @@ Variables.setMethod(function toHawkejs(wm) { dict = Bound.JSON.clone(dict, 'toHawkejs', wm); // Put the cloned values back into the result - Object.assign(result, dict); + result[VALUES] = mapify(dict); return result; }); @@ -242,25 +335,29 @@ Variables.setMethod(function getShallowClone() { * * @author Jelle De Loecker * @since 2.0.0 - * @version 2.3.7 + * @version 2.4.0 */ Variables.setMethod(function toJSON() { - let result, - key; + let result = this.getOwnDict(); if (this[PARENT]) { - result = this[PARENT].toJSON(); - } else { - result = {}; + result = {...this[PARENT].toJSON(), ...result}; } - // Copy all the keys without `Object.assign`, - // because that will also copy symbols - for (key in this) { - result[key] = this[key]; - } + return result; +}); +/** + * Create a new shim + * + * @author Jelle De Loecker + * @since 2.0.0 + * @version 2.4.0 + */ +Variables.setMethod(function createShim(new_variables) { + let result = new Variables(this[RENDERER], new_variables); + result[PARENT] = this[TRAPLESS]; return result; }); @@ -272,20 +369,5 @@ Variables.setMethod(function toJSON() { * @version 2.4.0 */ Variables.setMethod(function overlay(new_variables) { - - var result = this.createShim(); - - if (new_variables instanceof Variables) { - new_variables = new_variables.toJSON(); - } - - if (new_variables) { - Object.assign(result, new_variables); - } - - // This will set this instance as the new parent of the variables instance - // (This can overwrite the parent in the cloned instance) - result[PARENT] = this[TRAPLESS]; - - return result; + return this.createShim(new_variables); }); \ No newline at end of file diff --git a/lib/element/custom_element.js b/lib/element/custom_element.js index cd743b10..0a09ff1a 100644 --- a/lib/element/custom_element.js +++ b/lib/element/custom_element.js @@ -1025,27 +1025,17 @@ function renderCustomTemplate(re_render) { return next(); } - let result = that.prepareRenderVariables(); + Classes.Pledge.Swift.done(that.prepareRenderVariables(), (err, result) => { - if (result) { - if (Classes.Pledge.isThenable(result)) { - Classes.Pledge.Swift.done(result, (err, result) => { - - if (err) { - return next(err); - } - - render_vars = Bound.JSON.clone(result, 'toHawkejs'); - next(); - }); - - return; - } else { - render_vars = Bound.JSON.clone(result, 'toHawkejs'); + if (err) { + return next(err); } - } - next(); + render_vars = Hawkejs.Variables.cast(result, renderer); + render_vars = Bound.JSON.clone(render_vars, 'toHawkejs'); + + next(); + }); }, function renderTemplate(next) { if (alreadyDone()) { @@ -1061,9 +1051,9 @@ function renderCustomTemplate(re_render) { render_vars = Hawkejs.Variables.cast(that[Hawkejs.VARIABLES], renderer).overlay(render_vars); } - render_vars.self = that; - render_vars.child_nodes = slot_data.child_nodes; - render_vars.$ancestor_element = that; + render_vars.setFromTemplate('self', that); + render_vars.setFromTemplate('child_nodes', slot_data.child_nodes); + render_vars.setFromTemplate('$ancestor_element', that); render_vars[renderer.clone_symbol] = render_vars; @@ -1924,17 +1914,17 @@ Element.setMethod(function assignData(name, data) { * * @author Jelle De Loecker * @since 2.3.7 - * @version 2.3.7 + * @version 2.4.0 */ Element.setMethod(function setVariable(name, value) { let variables = this[Hawkejs.VARIABLES]; if (!variables) { - variables = this[Hawkejs.VARIABLES] = {}; + variables = this[Hawkejs.VARIABLES] = this.hawkejs_renderer.prepareVariables({}); } - variables[name] = value; + variables.set(name, value); }); /** @@ -1942,14 +1932,14 @@ Element.setMethod(function setVariable(name, value) { * * @author Jelle De Loecker * @since 2.3.7 - * @version 2.3.7 + * @version 2.4.0 */ Element.setMethod(function getVariable(name) { let variables = this[Hawkejs.VARIABLES]; - if (variables && variables[name] != null) { - return variables[name]; + if (variables && variables.has(name)) { + return variables.get(name); } if (this.parentElement) { @@ -2539,7 +2529,7 @@ Element.setMethod(function applyCompiledTemplate(fnc) { render_vars = renderer.prepareVariables({}); } - Blast.defineGet(render_vars, 'self', () => { + render_vars.setEphemeralGetter('self', () => { // Ensure the element options are ready this.ensureConstructed(); @@ -2547,7 +2537,7 @@ Element.setMethod(function applyCompiledTemplate(fnc) { return this; }); - Blast.defineGet(render_vars, '$ancestor_element', () => { + render_vars.setEphemeralGetter('$ancestor_element', () => { // Ensure the element options are ready this.ensureConstructed(); diff --git a/lib/expression/each.js b/lib/expression/each.js index 7f9db7b5..8ce3b7a9 100644 --- a/lib/expression/each.js +++ b/lib/expression/each.js @@ -63,7 +63,7 @@ Each.setStatic(function parseArguments(options) { * * @author Jelle De Loecker * @since 1.2.9 - * @version 2.3.19 + * @version 2.4.0 * * @param {String} name * @param {Array} pieces @@ -106,17 +106,17 @@ Each.setMethod(function execute() { if (!length) { return this; } - } else if (this.vars._$each_var != null) { - variable = this.vars._$each_var - as_name = this.vars._$each_as; - values = this.vars._$each_values; - keys = this.vars._$each_keys; + } else if (this.vars.has('_$each_var')) { + variable = this.vars.get('_$each_var'); + as_name = this.vars.get('_$each_as'); + values = this.vars.get('_$each_values'); + keys = this.vars.get('_$each_keys'); } else if (this.parent && this.parent.constructor.name == 'With') { base_vars = this.parent.prepareVars(); - variable = base_vars._$each_var; - values = base_vars._$each_values; - as_name = base_vars._$each_as; - keys = base_vars._$each_keys; + variable = base_vars.get('_$each_var'); + values = base_vars.get('_$each_values'); + as_name = base_vars.get('_$each_as'); + keys = base_vars.get('_$each_keys'); } if (!base_vars) { @@ -133,16 +133,17 @@ Each.setMethod(function execute() { return this; } - vars = Object.create(base_vars); - vars.$index = i; - vars.$key = keys[i]; + vars = base_vars.overlay(); + vars.setFromTemplate('$index', i); + vars.setFromTemplate('$key', keys[i]); if (as_key) { - vars[as_key] = vars.$key; + vars.setFromTemplate(as_key, keys[i]); } - vars.$value = values[i]; - vars[as_name] = values[i]; + vars.setFromTemplate('$value', values[i]); + vars.setFromTemplate(as_name, values[i]); + this.execExpressionFunction(this.fnc, vars); } } else if (this.branches && this.branches.else) { diff --git a/lib/expression/expression.js b/lib/expression/expression.js index 56d14dc4..97892f90 100644 --- a/lib/expression/expression.js +++ b/lib/expression/expression.js @@ -403,7 +403,7 @@ Expression.setMethod(function _overlayElementVariables(variables, element) { * * @author Jelle De Loecker * @since 2.3.1 - * @version 2.3.1 + * @version 2.4.0 * * @param {Array} path * @param {Object} variables @@ -426,7 +426,11 @@ Expression.setMethod(function _getValueByPath(path, variables, main_variables) { } } - current = current[piece]; + if (current && current instanceof Classes.Hawkejs.Variables) { + current = current.get(piece); + } else { + current = current[piece]; + } if (!current) { break; @@ -800,7 +804,7 @@ Expression.setMethod(function getTokenValue(token, vars) { * * @author Jelle De Loecker * @since 2.2.4 - * @version 2.2.4 + * @version 2.4.0 * * @param {Array} entries All the key-vals in an array */ @@ -809,8 +813,8 @@ Expression.setMethod(function setDefaultFromObjectLiteralExpression(entries, var let result = this.getObjectLiteralValue(entries, vars); for (let key in result) { - if (this.vars[key] == null) { - this.vars[key] = result[key]; + if (this.vars.get(key) == null) { + this.vars.set(key, result[key]); } } }); @@ -973,7 +977,7 @@ Expression.setMethod(function getArgumentObject(main_name, tokens, vars) { */ Expression.setMethod(function callPathWithArgs(path, args, vars) { - var options = [this.view.helpers, this.view, Blast.Globals], + let options = [this.view.helpers, this.view, Blast.Globals], context, result, temp, @@ -986,7 +990,7 @@ Expression.setMethod(function callPathWithArgs(path, args, vars) { } if (vars) { - options.unshift(vars); + options.unshift(vars.getProxy()); } if (path.length == 1) { diff --git a/lib/expression/set.js b/lib/expression/set.js index 701f4a9c..f63a65ca 100644 --- a/lib/expression/set.js +++ b/lib/expression/set.js @@ -45,7 +45,7 @@ Set.setStatic(function parseArguments(options) { * * @author Jelle De Loecker * @since 2.1.6 - * @version 2.2.0 + * @version 2.4.0 * * @param {Hawkejs.Parser.Builder} builder * @param {Hawkejs.Parser.Subroutine} subroutine @@ -56,7 +56,7 @@ Set.setStatic(function toCode(builder, subroutine, entry) { let options = entry.args[0], piece, code = '', - path = 'vars', + path = 'vars.getProxy()', i; for (i = 0; i < options.variable.length; i++) { diff --git a/lib/expression/with.js b/lib/expression/with.js index 81cf28cd..a3506e63 100644 --- a/lib/expression/with.js +++ b/lib/expression/with.js @@ -62,7 +62,7 @@ With.setStatic(function parseArguments(options) { * * @author Jelle De Loecker * @since 1.2.9 - * @version 2.3.19 + * @version 2.4.0 * * @param {String} name * @param {Array} pieces @@ -90,9 +90,8 @@ With.setMethod(function execute() { value = values[i]; // Create new variable object - vars = { - [options.as]: value, - }; + vars = new Hawkejs.Variables(this.view); + vars.setFromTemplate(options.as, value); // Parse the expression value = this.parseExpression(options.where, vars); @@ -118,16 +117,16 @@ With.setMethod(function execute() { this.variable = variable; // Create a new vars object - let main_vars = Object.create(this.vars); + let main_vars = this.vars.overlay(); // Add the "$amount" to it - main_vars.$amount = length; + main_vars.setFromTemplate('$amount', length); // Set some extra variables - main_vars._$each_as = options.as; - main_vars._$each_var = this.variable; - main_vars._$each_values = this.values; - main_vars._$each_keys = this.keys; + main_vars.setFromTemplate('_$each_as', options.as); + main_vars.setFromTemplate('_$each_var', this.variable); + main_vars.setFromTemplate('_$each_values', this.values); + main_vars.setFromTemplate('_$each_keys', this.keys); // And store it as the variables to use this.vars = main_vars; @@ -141,13 +140,10 @@ With.setMethod(function execute() { * * @author Jelle De Loecker * @since 1.2.9 - * @version 2.0.0 + * @version 2.4.0 */ With.setMethod(function prepareVars() { - - var vars = Object.create(this.vars); - - return vars; + return this.vars.overlay(); }); /** @@ -192,7 +188,7 @@ With.setMethod(function onBranch_all(options, fnc) { * * @author Jelle De Loecker * @since 1.2.9 - * @version 2.3.19 + * @version 2.4.0 * * @param {Object} options * @param {Function} fnc @@ -204,7 +200,7 @@ With.setMethod(function onBranch_single(options, fnc) { } let vars = this.prepareVars(); - vars[this.options.as] = this.values[0]; + vars.setFromTemplate(options.as, this.values[0]); this.execExpressionFunction(fnc, vars); }); diff --git a/test/02-variables.js b/test/02-variables.js index eb1398b5..4308704d 100644 --- a/test/02-variables.js +++ b/test/02-variables.js @@ -31,6 +31,13 @@ describe('Variables', () => { vars.getProxy().bla = 47; assert.strictEqual(vars.get('bla'), 47); + + const proxy = vars.getProxy(); + proxy.i = 0; + proxy.i++; + proxy.i++; + assert.strictEqual(vars.get('i'), 2); + assert.strictEqual(proxy.i, 2); }); }); @@ -97,6 +104,39 @@ describe('Variables', () => { }); }); + describe('#toHawkejs()', () => { + it('should be used as a JSON-DRY cloning method', () => { + + let vars = renderer.prepareVariables({alpha: 47, beta: 100, obj: {a: 1}}); + let cloned = Bound.JSON.clone(vars, 'toHawkejs'); + + assert.strictEqual(vars.get('alpha'), 47); + assert.strictEqual(cloned.get('alpha'), 47); + + let original_obj = vars.get('obj'); + let cloned_obj = cloned.get('obj'); + + assert.strictEqual(original_obj.a, 1); + assert.strictEqual(cloned_obj.a, 1); + + assert.deepStrictEqual(original_obj, cloned_obj); + assert.notStrictEqual(original_obj, cloned_obj); + assert.strictEqual(cloned instanceof Blast.Classes.Hawkejs.Variables, true); + + vars = Hawkejs.Variables.cast({obj: {a: 2}}, renderer); + cloned = Bound.JSON.clone(vars, 'toHawkejs'); + + original_obj = vars.get('obj'); + cloned_obj = cloned.get('obj'); + + assert.strictEqual(original_obj.a, 2); + assert.strictEqual(cloned_obj.a, 2); + assert.deepStrictEqual(original_obj, cloned_obj); + assert.notStrictEqual(original_obj, cloned_obj); + assert.strictEqual(cloned instanceof Blast.Classes.Hawkejs.Variables, true); + }); + }); + describe('#getShallowClone()', () => { it('should return a shallow clone', () => { diff --git a/test/05-custom_elements.js b/test/05-custom_elements.js index b16489e9..338d3f6a 100644 --- a/test/05-custom_elements.js +++ b/test/05-custom_elements.js @@ -757,6 +757,12 @@ describe('CustomElement', function() { let data = await getBlockData('main'); + let has_missing_data_index = data.html.indexOf('nested-async>DONE<') > -1; + + if (has_missing_data_index) { + throw new Error('The nested-async element has rendered, but it is missing the `data-index="0"` attribute'); + } + let has_done = data.html.indexOf('nested-async data-index="0">DONE<') > -1; assert.strictEqual(has_done, true, 'The nested elements did not render fully'); diff --git a/test/helpers/with_reactive_attributes.js b/test/helpers/with_reactive_attributes.js new file mode 100644 index 00000000..043644f1 --- /dev/null +++ b/test/helpers/with_reactive_attributes.js @@ -0,0 +1,11 @@ +const WithReactiveAttributes = Fn.inherits('Hawkejs.Element', 'WithReactiveAttributes'); + +WithReactiveAttributes.setTemplate(` +
+ {% if @is_active %} + Active! + {% else %} + Not active! + {% /if %} +
+`); \ No newline at end of file