diff --git a/src/build.mjs b/src/build.mjs index c83f181..05e63fc 100644 --- a/src/build.mjs +++ b/src/build.mjs @@ -82,8 +82,8 @@ export const evaluate = (h, built, fields, args) => { for (let i = 1; i < built.length; i++) { const type = built[i++]; - // Set `built[0]` to truthy if this element depends on a dynamic value. - const value = built[i] ? fields[built[0] = built[i++]] : built[++i]; + // Set `built[0]`'s appropriate bits if this element depends on a dynamic value. + const value = built[i] ? ((built[0] |= type ? 1 : 2), fields[built[i++]]) : built[++i]; if (type === TAG_SET) { args[0] = value; @@ -97,14 +97,15 @@ export const evaluate = (h, built, fields, args) => { else if (type === PROP_APPEND) { args[1][built[++i]] += (value + ''); } - else if (type) { - // type === CHILD_RECURSE - tmp = h.apply(0, evaluate(h, value, fields, ['', null])); + else if (type) { // type === CHILD_RECURSE + // Set the operation list (including the staticness bits) as + // `this` for the `h` call. + tmp = h.apply(value, evaluate(h, value, fields, ['', null])); args.push(tmp); if (value[0]) { - // If the child element is dynamic, then so is the current element. - built[0] = 1; + // Set the 2nd lowest bit it the child element is dynamic. + built[0] |= 2; } else { // Rewrite the operation list in-place if the child element is static. @@ -116,8 +117,7 @@ export const evaluate = (h, built, fields, args) => { built[i] = tmp; } } - else { - // type === CHILD_APPEND + else { // type === CHILD_APPEND args.push(value); } } diff --git a/test/statics-caching.test.mjs b/test/statics-caching.test.mjs index 68641f7..11c9820 100644 --- a/test/statics-caching.test.mjs +++ b/test/statics-caching.test.mjs @@ -36,4 +36,57 @@ describe('htm', () => { expect(a).toBe(1); expect(b).toBe(2); }); + + describe('`this` in the h function', () => { + const html = htm.bind(function() { + return this; + }); + + test('stays the same for each call site)', () => { + const x = () => html`
a
`; + const a = x(); + const b = x(); + expect(a).toBe(b); + }); + + test('is different for each call site', () => { + const a = html`
a
`; + const b = html`
a
`; + expect(a).not.toBe(b); + }); + + test('is specific to each h function', () => { + let tmp = htm.bind(function() { return this; }); + const x = () => tmp`
a
`; + const a = x(); + tmp = htm.bind(function() { return this; }); + const b = x(); + expect(a).not.toBe(b); + }); + }); + + describe('`this[0]` in the h function contains the staticness bits', () => { + const html = htm.bind(function() { + return this[0]; + }); + + test('should be 0 for static subtrees', () => { + expect(html`
`).toBe(0); + expect(html`
a
`).toBe(0); + expect(html`
`).toBe(0); + }); + + test('should be 2 for static nodes with some dynamic children', () => { + expect(html`
${'a'}
`).toBe(2); + expect(html`
`).toBe(2); + }); + + test('should be 1 for dynamic nodes with all static children', () => { + expect(html`
`).toBe(1); + }); + + test('should be 3 for dynamic nodes with some dynamic children', () => { + expect(html`
`).toBe(3); + }); + }); });