Skip to content

Commit

Permalink
basic string child node render and update!
Browse files Browse the repository at this point in the history
  • Loading branch information
martypdx committed Mar 16, 2024
1 parent 04595e8 commit a63005b
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 80 deletions.
164 changes: 94 additions & 70 deletions packages/runtime/renderer/magic.test.js
Original file line number Diff line number Diff line change
@@ -1,98 +1,122 @@
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', `<p data-bind><!--0--></p>`);
const stringSource = makeStringRenderer('id', [`<p data-bind>`, `</p>`]);

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 => {
compose(t0, p0);
};
};

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 => <span>{greeting}</span>);
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 <p>
<Greeting greeting={greeting} /> {name}
</p>;
});
class Controller {
static for(renderFn) {
return new this(renderFn);
}
constructor(renderFn) {
this.renderFn = renderFn;
}
render(props) {
return this.renderFn(props);
}
update(dom, props) {
injectRender(dom, () => this.renderFn(props));
}
}
const Hello = Controller.for(name => <p>{name}</p>);
*/
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(
`"<p data-bind>felix</p>"`
);
expect(flatRender(node2)).toMatchInlineSnapshot(
`"<p data-bind>duchess</p>"`
);

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(
`"<p data-bind>garfield</p>"`
);
expect(flatRender(node2)).toMatchInlineSnapshot(
`"<p data-bind>stimpy</p>"`
);
});

let dom1 = controller.render('felix');
let dom2 = controller.render('duchess');
expect(dom1.outerHTML).toMatchInlineSnapshot(`"<p data-bind="">felix<!--1--></p>"`);
expect(dom2.outerHTML).toMatchInlineSnapshot(`"<p data-bind="">duchess<!--1--></p>"`);
test('Updater.for', ({ expect }) => {
const updater = Updater.for(name => renderString(name));
const node = updater.render('felix');
expect(flatRender(node)).toMatchInlineSnapshot(
`"<p data-bind>felix</p>"`
);

updater.update('duchess');
expect(flatRender(node)).toMatchInlineSnapshot(
`"<p data-bind>duchess</p>"`
);
});

controller.update(dom1, 'garfield');
controller.update(dom2, 'stimpy');
expect(dom1.outerHTML).toMatchInlineSnapshot(`"<p data-bind="">garfield<!--1--></p>"`);
expect(dom2.outerHTML).toMatchInlineSnapshot(`"<p data-bind="">stimpy<!--1--></p>"`);
});
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(`"<p data-bind="">felix<!--1--></p>"`);
expect(node2.outerHTML).toMatchInlineSnapshot(`"<p data-bind="">duchess<!--1--></p>"`);

controller.update(node1, 'garfield');
controller.update(node2, 'stimpy');
expect(node1.outerHTML).toMatchInlineSnapshot(`"<p data-bind="">garfield<!--1--></p>"`);
expect(node2.outerHTML).toMatchInlineSnapshot(`"<p data-bind="">stimpy<!--1--></p>"`);
});

test('Updater.for', ({ expect }) => {
const updater = Updater.for(name => render123(name));
const node = updater.render('felix');
expect(node.outerHTML).toMatchInlineSnapshot(`"<p data-bind="">felix<!--1--></p>"`);

test('update remembers dom', ({ expect }) => {
const updater = Updater.for(name => render123(name));
const dom = updater.render('felix');
expect(dom.outerHTML).toMatchInlineSnapshot(`"<p data-bind="">felix<!--1--></p>"`);
updater.update('duchess');
expect(node.outerHTML).toMatchInlineSnapshot(`"<p data-bind="">duchess<!--1--></p>"`);
});

updater.update('duchess');
expect(dom.outerHTML).toMatchInlineSnapshot(`"<p data-bind="">duchess<!--1--></p>"`);
});
74 changes: 64 additions & 10 deletions packages/runtime/renderer/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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];
};*/
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);
}
}

0 comments on commit a63005b

Please sign in to comment.