///
import * as ActionSpec from "./Action";
ActionSpec.include;
import * as reactiv from 'reactiv';
export interface messageProps {
importance: number;
message: string;
}
export class message extends reactiv.Component {
render() {
reactiv.elementOpen("div", null, null, "style", { display: "inline", color: this.props.importance > 5 ? "red" : "gray" });
reactiv.text(this.props.message);
reactiv.elementClose();
}
}
export interface importantProps {
importance: number;
name: string;
}
export interface importantState {
tired: boolean;
}
export class important extends reactiv.Component {
constructor(props: importantProps) {
super(props);
this.setState({ tired: false });
}
render() {
reactiv.elementVoid(message as any, null, null, "importance", this.props.importance, "message", this.state.tired ? "tired" : "ok");
}
}
let lc_methods = [];
let lc_constructor = 0; // check
let lc_componentWillMount = 0; // check
let lc_componentDidMount = 0; // check
let lc_componentWillReceiveProps = 0; // todo! needs to check args before
let lc_shouldComponentUpdate = 0; // check
let lc_componentWillUpdate = 0; // check
let lc_componentDidUpdate = 0; // check
let lc_componentWillUnmount = 0; // check
let lc_render = 0;// check
var freeze_message = false;
export class lifecycle {
constructor() {
lc_methods.push("constructor");
lc_constructor++;
}
componentWillMount() {
lc_methods.push("componentWillMount");
lc_componentWillMount++;
}
componentDidMount() {
lc_methods.push("componentDidMount");
lc_componentDidMount++;
}
componentWillReceiveProps(nextProps) {
lc_methods.push("componentWillReceiveProps");
lc_componentWillReceiveProps++;
}
shouldComponentUpdate(nextProps, nextState) {
lc_methods.push("shouldComponentUpdate");
lc_shouldComponentUpdate++;
return !freeze_message;
}
componentWillUpdate(nextProps, nextState) {
lc_methods.push("componentWillUpdate");
lc_componentWillUpdate++;
}
componentDidUpdate(prevProps, prevState) {
lc_methods.push("componentDidUpdate");
lc_componentDidUpdate++;
}
componentWillUnmount() {
lc_methods.push("componentWillUnmount");
lc_componentWillUnmount++;
}
render() {
lc_methods.push("render");
lc_render++;
reactiv.elementVoid("div", null, null, "frozen", freeze_message ? "yes" : "no");
}
}
function simulateClick(element: Element) {
var simulateDivClick = document.createEvent('MouseEvents');
simulateDivClick.initMouseEvent('click', true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, null, null);
return element.dispatchEvent(simulateDivClick);
}
describe("a patch", () => {
var node: HTMLElement;
var starting_html: string;
var original_properties_of = [];
var get_properties = (container) => Object.getOwnPropertyNames(container);
beforeEach((done) => {
starting_html = document.body.innerHTML;
node = document.createElement("div");
document.body.appendChild(node);
setTimeout(() => {
var conserve_properties_of = [window, document, document.body];
original_properties_of = conserve_properties_of.map(container => get_properties(container))
lc_methods = [];
lc_constructor = 0;
lc_componentWillMount = 0;
lc_componentDidMount = 0;
lc_componentWillReceiveProps = 0;
lc_shouldComponentUpdate = 0;
lc_componentWillUpdate = 0;
lc_componentDidUpdate = 0;
lc_componentWillUnmount = 0;
lc_render = 0;
freeze_message = false;
done();
});
});
afterEach(() => {
document.body.removeChild(node);
expect(document.body.innerHTML.trim()).toBe(starting_html.trim());
var conserve_properties_of = [window, document, document.body];
var new_properties_of = conserve_properties_of.map(container => get_properties(container))
expect(new_properties_of).toEqual(original_properties_of);
});
it("does nothing if nothing is rendered", () => {
reactiv.patch(node, () => null);
expect(node.outerHTML).toBe("");
node.setAttribute("data-id", "3");
node.innerHTML = "somestuff";
reactiv.patch(node, () => null);
expect(node.outerHTML).toBe('somestuff
');
});
it("can use elementVoid to insert a div", () => {
reactiv.patch(node, () => {
reactiv.elementVoid("div");
});
expect(node.outerHTML).toBe("");
});
it("will remove something", () => {
reactiv.patch(node, () => {
reactiv.elementOpen("span", null, null);
reactiv.elementClose();
});
expect(node.outerHTML).toBe("
");
reactiv.patch(node, () => null);
expect(node.outerHTML).toBe("");
});
it("can replace and add things", () => {
reactiv.patch(node, () => {
reactiv.elementOpen("div", null, null);
reactiv.elementClose();
});
reactiv.patch(node, () => {
reactiv.elementOpen("div", null, null);
reactiv.elementClose();
reactiv.elementOpen("div", null, null);
reactiv.elementClose();
});
expect(node.outerHTML).toBe("");
});
it("doesn't re-add the same div", () => {
var fn = () =>
reactiv.patch(node, () => {
reactiv.elementOpen("div", null, null);
reactiv.elementClose();
});
for (var i = 0; i < 3; i++) {
fn();
expect(node.outerHTML).toBe("");
}
});
it("handles attributes", () => {
reactiv.patch(node, () => {
reactiv.elementOpen("div", null, null, 'id', 'the_id', "style", { color: 'red' });
reactiv.elementClose();
});
expect(node.outerHTML).toBe('');
});
it("mutates attributes", () => {
var style = { color: 'red' } as any;
reactiv.patch(node, () => {
reactiv.elementOpen("div", null, null, 'id', 'the_id', "style", style);
reactiv.elementClose();
});
expect(node.outerHTML).toBe('');
style.color = "blue";
reactiv.patch(node, () => {
reactiv.elementOpen("div", null, null, 'id', 'the_id', "style", style);
reactiv.elementClose();
});
style.backgroundColor = "red";
delete style.color;
reactiv.patch(node, () => {
reactiv.elementOpen("div", null, null, 'id', 'the_id', "style", style);
reactiv.elementClose();
});
expect(node.outerHTML).toBe('');
});
it("adds attributes", () => {
reactiv.patch(node, () => {
reactiv.elementOpen("div", null, null, 'id', 'the_id');
reactiv.elementClose();
});
expect(node.outerHTML).toBe('');
reactiv.patch(node, () => {
reactiv.elementOpen("div", null, null, 'id', 'the_id', "name", "fred");
reactiv.elementClose();
});
expect(node.outerHTML).toBe('');
});
it("removes attributes", () => {
reactiv.patch(node, () => {
reactiv.elementOpen("div", null, null, 'id', 'the_id', "name", "fred");
reactiv.elementClose();
});
expect(node.outerHTML).toBe('');
reactiv.patch(node, () => {
reactiv.elementVoid("div", null, null, 'id', 'the_id');
});
expect(node.outerHTML).toBe('');
reactiv.patch(node, () => {
reactiv.elementVoid("div", null, ['id', 'the_id', 'frodo', '121'], 'data-frame', 'anterior');
});
expect(node.outerHTML).toBe('');
});
it("deals with a component", () => {
reactiv.patch(node, () => {
reactiv.elementVoid(lifecycle as any);
});
expect(node.outerHTML).toBe('');
});
it("adds event handlers", () => {
var prevent_click_default = false;
var count = 0;
var test_click = (event) => {
count++;
if (prevent_click_default)
event.preventDefault();
}
reactiv.patch(node, () => {
reactiv.elementVoid("div", null, ["onClick", test_click]);
});
expect(node.outerHTML).toBe('');
var cancelled = !simulateClick(node.children[0]);
expect(cancelled).toBe(false);
expect(count).toBe(1);
prevent_click_default = true;
var cancelled = !simulateClick(node.children[0]);
expect(cancelled).toBe(true);
expect(count).toBe(2);
reactiv.patch(node, () => {
reactiv.elementVoid("div", null, ["onClick", test_click]);
});
prevent_click_default = false;
var cancelled = !simulateClick(node.children[0]);
expect(cancelled).toBe(false);
expect(count).toBe(3);
});
it("removes event handlers", () => {
var prevent_click_default = false;
var count = 0;
var test_click = (event) => {
count++;
if (prevent_click_default)
event.preventDefault();
}
reactiv.patch(node, () => {
reactiv.elementVoid("div", null, ["onClick", test_click]);
});
expect(node.outerHTML).toBe('');
var cancelled = !simulateClick(node.children[0]);
expect(cancelled).toBe(false);
expect(count).toBe(1);
prevent_click_default = true;
var cancelled = !simulateClick(node.children[0]);
expect(cancelled).toBe(true);
expect(count).toBe(2);
reactiv.patch(node, () => {
reactiv.elementVoid("div", null, null);
});
var cancelled = !simulateClick(node.children[0]);
expect(cancelled).toBe(false);
expect(count).toBe(2);
});
it("changes event handlers", () => {
var count = 0;
var test_click = (event) => {
count++;
}
var test_click2 = (event) => {
count += 100;
event.preventDefault();
}
reactiv.patch(node, () => {
reactiv.elementVoid("div", null, ["onClick", test_click]);
});
expect(node.outerHTML).toBe('');
var cancelled = !simulateClick(node.children[0]);
expect(cancelled).toBe(false);
expect(count).toBe(1);
reactiv.patch(node, () => {
reactiv.elementVoid("div", null, ["onClick", test_click2]);
});
var cancelled = !simulateClick(node.children[0]);
expect(cancelled).toBe(true);
expect(count).toBe(101);
});
it("allows elements to mutate themselves", () => {
reactiv.patch(node, () => reactiv.elementVoid("div"));
(node.children[0] as HTMLElement).innerHTML = "dynamic";
reactiv.patch(node, () => reactiv.elementVoid("div"));
expect(node.outerHTML).toBe('');
});
it("constructs components appropriately", () => {
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_constructor).toBe(1);
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_constructor).toBe(1);
reactiv.patch(node, () => null);
expect(lc_constructor).toBe(1);
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_constructor).toBe(2);
});
it("calls componentWillMount appropriately", () => {
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_componentWillMount).toBe(1);
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_componentWillMount).toBe(1);
});
it("calls componentDidMount appropriately", () => {
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_componentDidMount).toBe(1);
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_componentDidMount).toBe(1);
});
it("calls componentWillUnmount appropriately", () => {
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_componentWillUnmount).toBe(0);
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_componentWillUnmount).toBe(0);
reactiv.patch(node, () => null);
expect(lc_componentWillUnmount).toBe(1);
});
it("calls componentWillUpdate appropriately", () => {
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_componentWillUpdate).toBe(0);
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_componentWillUpdate).toBe(1);
reactiv.patch(node, () => null);
expect(lc_componentWillUpdate).toBe(1);
});
it("calls componentDidUpdate appropriately", () => {
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_componentDidUpdate).toBe(0);
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_componentDidUpdate).toBe(1);
reactiv.patch(node, () => null);
expect(lc_componentDidUpdate).toBe(1);
});
it("calls shouldComponentUpdate appropriately", () => {
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_shouldComponentUpdate).toBe(0);
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_shouldComponentUpdate).toBe(1);
reactiv.patch(node, () => null);
expect(lc_shouldComponentUpdate).toBe(1);
});
it("calls mounting lifecycle methods in the correct order", () => {
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_methods.join(" => ")).toBe("constructor => componentWillMount => render => componentDidMount");
});
it("calls re-render lifecycle methods in the correct order", () => {
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
lc_methods = [];
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_methods.join(" => ")).toBe("componentWillReceiveProps => shouldComponentUpdate => componentWillUpdate => render => componentDidUpdate");
});
it("respects shouldComponentUpdate", () => {
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
lc_methods = [];
freeze_message = true;
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
expect(lc_methods.join(" => ")).toBe("componentWillReceiveProps => shouldComponentUpdate");
expect(node.outerHTML).toBe('');
});
it("calls unmounting lifecycle methods in the correct order", () => {
reactiv.patch(node, () => reactiv.elementVoid(lifecycle as any));
lc_methods = [];
reactiv.patch(node, () => null);
expect(lc_methods.join(" => ")).toBe("componentWillUnmount");
});
it("unmounts components", () => {
reactiv.patch(node, () => {
reactiv.elementVoid(lifecycle as any, null, null, null);
});
expect(node.outerHTML).toBe('');
expect(lc_componentWillMount).toBe(1);
expect(lc_componentDidMount).toBe(1);
reactiv.patch(node, () => {
reactiv.elementVoid("div", null, null, null);
});
expect(node.outerHTML).toBe('');
expect(lc_componentWillMount).toBe(1);
expect(lc_componentDidMount).toBe(1);
expect(lc_componentWillUnmount).toBe(1);
});
it("recognises nested components", () => {
var start = new Date().getTime();
reactiv.patch(node, () => {
reactiv.elementVoid(important as any, null, null, "importance", 7, "name", "bond, jimmy-bob melon-field bond");
});
expect(node.outerHTML).toBe('');
});
it("is fast", () => {
const fn = () =>
reactiv.patch(node, () =>
reactiv.elementVoid(important as any, null, null, "importance", i % 10, "name", "bond, jimmy-bob " + (i % 2 ? "melon-field" : "princess") + " bond"));
const iterations = 10000;
//warm-up
for (var i = 0; i < iterations/10; i++)
fn();
var start = new Date().getTime();
for (var i = 0; i < iterations; i++)
fn();
var duration = new Date().getTime() - start;
console.log("benchmark: " + iterations + " took " + duration + " ms = " + (Math.ceil(duration / iterations * 10000) / 10) + " us per");
expect(duration).toBeLessThan(1000);
});
it("deals with keys", () => {
reactiv.patch(node, () => {
reactiv.elementVoid("div", "1", null, "id", "iamme");
});
expect(node.outerHTML).toBe('');
reactiv.patch(node, () => {
reactiv.elementVoid("div", "1", null, "id", "iamstillme");
});
expect(node.outerHTML).toBe('');
});
});