diff --git a/packages/runtime/renderer/magic.test.js b/packages/runtime/renderer/magic.test.js index 2c8c7f7..3044438 100644 --- a/packages/runtime/renderer/magic.test.js +++ b/packages/runtime/renderer/magic.test.js @@ -1,20 +1,15 @@ -import { test } from 'vitest'; +import { describe, test } from 'vitest'; import { compose } from '../compose/compose.js'; -import { makeRenderer, getBoundElements } from './renderer.js'; +import { makeRenderer, makeTemplate, Controller, Updater, makeStringRenderer } from './renderer.js'; // template generated artifacts const source = makeRenderer('id', `
`); +const stringSource = makeStringRenderer('id', [``, `
`]); -let injectableRoot = null; -function injectRender(dom, callback) { - injectableRoot = dom; - callback(); - injectableRoot = null; -} - -function getBindTargets(r, boundEls) { +function getTargets(r, boundEls) { return [r.childNodes[0]]; } + const makeBind = targets => { const t0 = targets[0]; return p0 => { @@ -22,77 +17,106 @@ const makeBind = targets => { }; }; +function getStringTargets(r, boundEls) { + return [boundEls[0]]; +} + +const makeStringBind = targets => { + const t0 = targets[0]; + return p0 => { + t0[0] = p0; + }; +}; function render123(p0) { - const [root, bind] = makeTemplate(source); + const [root, bind] = makeTemplate( + source, + getTargets, + makeBind + ); bind(p0); return root; } +function renderString(p0) { + const [root, bind] = makeTemplate( + stringSource, + getStringTargets, + makeStringBind + ); + bind(p0); + return root; +} +/* +const NameTag = Controller.for(({ greeting, name }) => { + const Greeting = Controller.for(greeting => {greeting}); -const map = new Map(); - -const makeTemplate = (source) => { - let bind = null; - let root = injectableRoot; - // TODO: test injectable is right template id - - if(root) bind = map.get(root); - if(!bind) { - const result = root - ? [root, getBoundElements(root)] - : source(); - root = result[0]; - const nodes = getBindTargets(root, result[1]); - bind = makeBind(nodes); - map.set(root, bind); - } - - return [root, bind]; -}; + return
+
{name}
); +*/ +describe('string render', () => { + const flatRender = node => node.flat().join(''); + test('Controller.for', ({ expect }) => { + const controller = Controller.for(name => renderString(name)); -class Updater extends Controller { - #dom = null; - render(props) { - return this.#dom = super.render(props); - } - update(props) { - super.update(this.#dom, props); - } -} + let node1 = controller.render('felix'); + let node2 = controller.render('duchess'); + expect(flatRender(node1)).toMatchInlineSnapshot( + `"felix
"` + ); + expect(flatRender(node2)).toMatchInlineSnapshot( + `"duchess
"` + ); -test('controller creates or injects', ({ expect }) => { - const controller = Controller.for(name => render123(name)); + controller.update(node1, 'garfield'); + controller.update(node2, 'stimpy'); + expect(flatRender(node1)).toMatchInlineSnapshot( + `"garfield
"` + ); + expect(flatRender(node2)).toMatchInlineSnapshot( + `"stimpy
"` + ); + }); - let dom1 = controller.render('felix'); - let dom2 = controller.render('duchess'); - expect(dom1.outerHTML).toMatchInlineSnapshot(`"felix
"`); - expect(dom2.outerHTML).toMatchInlineSnapshot(`"duchess
"`); + test('Updater.for', ({ expect }) => { + const updater = Updater.for(name => renderString(name)); + const node = updater.render('felix'); + expect(flatRender(node)).toMatchInlineSnapshot( + `"felix
"` + ); + + updater.update('duchess'); + expect(flatRender(node)).toMatchInlineSnapshot( + `"duchess
"` + ); + }); - controller.update(dom1, 'garfield'); - controller.update(dom2, 'stimpy'); - expect(dom1.outerHTML).toMatchInlineSnapshot(`"garfield
"`); - expect(dom2.outerHTML).toMatchInlineSnapshot(`"stimpy
"`); }); +describe('dom render', () => { + + test('Controller.for', ({ expect }) => { + const controller = Controller.for(name => render123(name)); + + let node1 = controller.render('felix'); + let node2 = controller.render('duchess'); + expect(node1.outerHTML).toMatchInlineSnapshot(`"felix
"`); + expect(node2.outerHTML).toMatchInlineSnapshot(`"duchess
"`); + + controller.update(node1, 'garfield'); + controller.update(node2, 'stimpy'); + expect(node1.outerHTML).toMatchInlineSnapshot(`"garfield
"`); + expect(node2.outerHTML).toMatchInlineSnapshot(`"stimpy
"`); + }); + + test('Updater.for', ({ expect }) => { + const updater = Updater.for(name => render123(name)); + const node = updater.render('felix'); + expect(node.outerHTML).toMatchInlineSnapshot(`"felix
"`); -test('update remembers dom', ({ expect }) => { - const updater = Updater.for(name => render123(name)); - const dom = updater.render('felix'); - expect(dom.outerHTML).toMatchInlineSnapshot(`"felix
"`); + updater.update('duchess'); + expect(node.outerHTML).toMatchInlineSnapshot(`"duchess
"`); + }); - updater.update('duchess'); - expect(dom.outerHTML).toMatchInlineSnapshot(`"duchess
"`); }); diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index 5aa61bd..717575a 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -4,6 +4,21 @@ export function clearTemplates() { templates.clear(); } +export function makeStringRenderer(id, html, isFragment = false) { + return () => { + const root = []; + const targets = []; + for(let i = 0; i < html.length; i++) { + root.push(html[i]); + if(i === html.length - 1) break; + const target = []; + targets.push(target); + root.push(target); + } + return [root, targets]; + }; +} + export function makeRenderer(id, html, isFragment = false) { if(templates.has(id)) return templates.get(id); @@ -47,23 +62,62 @@ export function getBoundElements(dom) { } -/* + const map = new Map(); + + +export const injectable = []; +export function inject(node, callback) { + injectable.push(node); + callback(); + const popped = injectable.pop(); + if(popped !== node) { + // TODO: display html like object for compose + throw new Error('Injectable stack error'); + } +} + + export function makeTemplate(source, targets, makeBind) { let bind = null; - let root = injectableRoot; + let node = injectable.at(-1); // peek! // TODO: test injectable is right template id - if(root) bind = map.get(root); + if(node) bind = map.get(node); if(!bind) { - const result = root - ? [root, getBoundElements(root)] + const result = node + ? [node, getBoundElements(node)] : source(); - root = result[0]; - const nodes = targets(root, result[1]); + node = result[0]; + const nodes = targets(node, result[1]); bind = makeBind(nodes); - map.set(root, bind); + map.set(node, bind); } - return [root, bind]; -};*/ \ No newline at end of file + return [node, bind]; +} + +export class Controller { + static for(renderFn) { + return new this(renderFn); + } + constructor(renderFn) { + this.renderFn = renderFn; + } + render(props) { + return this.renderFn(props); + } + update(node, props) { + inject(node, () => this.renderFn(props)); + } +} + +export class Updater extends Controller { + #dom = null; + render(props) { + return this.#dom = super.render(props); + } + update(props) { + super.update(this.#dom, props); + } +} \ No newline at end of file