Add a buncha tests.
parent
d4a145b96b
commit
cae8aba97f
16
README.md
16
README.md
|
@ -33,18 +33,14 @@ import {
|
|||
transform, // transform an AST according to plugins
|
||||
} from '@thefarce/loom'
|
||||
|
||||
console.log(
|
||||
renderHtml(
|
||||
interpolate(
|
||||
transform(
|
||||
parseHtml('<p>Hello, {name}!</p>'),
|
||||
() => { ... },
|
||||
[funcA, funcB, funcC],
|
||||
),
|
||||
const initial = htmlToAst('<p>Hello, {name}!</p>'),
|
||||
|
||||
const transformed = interpolate(
|
||||
transform(initial, ...transformers),
|
||||
{ name: 'Othello' },
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
console.log(astToHtml(transformed));
|
||||
```
|
||||
|
||||
Running this script produces the following output:
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# Transformation
|
||||
|
||||
In Loom, *transformation* refers to the process of modifying an abstract
|
||||
syntax tree (AST), especially through the use of *transformers*.
|
||||
|
||||
Transformers are arrays of functions.
|
||||
syntax tree (AST), especially through the use of *transformation functions*.
|
||||
|
||||
The mechanism provided by loom for these transformations is the
|
||||
`transform()` function.
|
||||
|
@ -12,14 +10,14 @@ The first argument to the `transform` function is an AST. Each node in the
|
|||
AST will be considered for transformation.
|
||||
|
||||
After the AST, any number of additional arguments may be passed to the
|
||||
`transform` function. Each of these should be a *transformer function* or
|
||||
an array of such.
|
||||
`transform` function. Each of these should be a *transformation function*
|
||||
or an array of such.
|
||||
|
||||
## Transformation functions
|
||||
|
||||
Transformer functions take _a copy_ of an AST node. Because it is a copy,
|
||||
the changes made to the node received by the transformer function will have
|
||||
no effect on the tree. To change the tree, return a new AST node.
|
||||
the changes made to the node received by the transformation function will
|
||||
have no effect on the tree. To change the tree, return a new AST node.
|
||||
|
||||
### Return values
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
export function interpolate () {
|
||||
|
||||
}
|
||||
|
||||
export default interpolate;
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
export function transform () {
|
||||
|
||||
}
|
||||
|
||||
export default transform;
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import astToHtml from '../src/ast-to-html.mjs';
|
||||
import htmlAstMapping from './fixtures/ast-html.fixture.mjs';
|
||||
|
||||
describe("ast-to-html", () => {
|
||||
it("a UnifiedJS AST should be converted to html", () => {
|
||||
|
||||
expect(astToHtml).toBeDefined();
|
||||
|
||||
// Loop through all the mappings of html to AST and make sure they work.
|
||||
for (const [html, ast] of Object.entries(htmlAstMapping)) {
|
||||
expect(astToHtml(ast)).toEqual(html);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import div from "./html-ast.fixture.div.mjs";
|
||||
|
||||
export default Object.assign({},
|
||||
div
|
||||
);
|
|
@ -0,0 +1,40 @@
|
|||
export default {
|
||||
'<div></div>': {
|
||||
"type": "root",
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"tagName": "div",
|
||||
"properties": {},
|
||||
"children": [],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"data": {
|
||||
"quirksMode": false,
|
||||
},
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
import div from "./html-ast.fixture.div.mjs";
|
||||
|
||||
export default Object.assign({},
|
||||
div
|
||||
);
|
|
@ -1,53 +1,14 @@
|
|||
import htmlToAst from '../src/html-to-ast.mjs';
|
||||
import htmlAstMapping from './fixtures/html-ast.fixture.mjs';
|
||||
|
||||
describe("html-to-ast", () => {
|
||||
it("should pass", () => {
|
||||
it("html should be converted to a UnifiedJS AST", () => {
|
||||
|
||||
expect(htmlToAst).toBeDefined();
|
||||
|
||||
const validTransformations = {
|
||||
'<div></div>': {
|
||||
"type": "root",
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"tagName": "div",
|
||||
"properties": {},
|
||||
"children": [],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"data": {
|
||||
"quirksMode": false,
|
||||
},
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (const [html, expected] of Object.entries(validTransformations)) {
|
||||
expect(htmlToAst(html)).toEqual(expected);
|
||||
// Loop through all the mappings of html to AST and make sure they work.
|
||||
for (const [html, ast] of Object.entries(htmlAstMapping)) {
|
||||
expect(htmlToAst(html)).toEqual(ast);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import interpolate from '../src/interpolate.mjs';
|
||||
import htmlToAst from '../src/html-to-ast.mjs';
|
||||
import astToHtml from '../src/ast-to-html.mjs';
|
||||
|
||||
describe("interpolate()", () => {
|
||||
|
||||
it("should import", () => {
|
||||
expect(interpolate).toBeDefined();
|
||||
})
|
||||
|
||||
it("should interpolate", () => {
|
||||
|
||||
const uninterpolated = '<div class="${classname}"></div>';
|
||||
const expected = '<div class="foo"></div>';
|
||||
|
||||
const data = { classname: "foo" };
|
||||
const ast = htmlToAst(uninterpolated);
|
||||
const interpolated = interpolate(ast, data);
|
||||
const rendered = astToHtml(interpolated);
|
||||
|
||||
expect(rendered).toEqual(expected);
|
||||
})
|
||||
|
||||
});
|
|
@ -0,0 +1,263 @@
|
|||
import transform from '../src/transform.mjs';
|
||||
import htmlToAst from '../src/html-to-ast.mjs';
|
||||
|
||||
describe("transform", () => {
|
||||
|
||||
it("should import transform()", () => {
|
||||
expect(transform).toBeDefined();
|
||||
});
|
||||
|
||||
it("should throw an error if there's no AST", () => {
|
||||
expect(transform).toBeDefined();
|
||||
expect(() => transform()).toThrow();
|
||||
});
|
||||
|
||||
it("should act as an identity function if there are no tx fxns", () => {
|
||||
const html = '<div></div>';
|
||||
const ast = htmlToAst(html);
|
||||
|
||||
expect(transform(ast)).toEqual({
|
||||
"type": "root",
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"tagName": "div",
|
||||
"properties": {},
|
||||
"children": [],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should leave the ast untouched for null", () => {
|
||||
const html = '<div></div>';
|
||||
const ast = htmlToAst(html);
|
||||
|
||||
expect(transform(ast, () => null)).toEqual({
|
||||
"type": "root",
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"tagName": "div",
|
||||
"properties": {},
|
||||
"children": [],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should remove the node for undefined", () => {
|
||||
const html = '<div></div>';
|
||||
const ast = htmlToAst(html);
|
||||
|
||||
expect(transform(ast, () => undefined)).toEqual({
|
||||
"type": "root",
|
||||
"children": [],
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle multiple tx fxns", () => {
|
||||
const html = '<div></div>';
|
||||
const ast = htmlToAst(html);
|
||||
|
||||
expect(transform(
|
||||
ast,
|
||||
() => ({
|
||||
"type": "root",
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"tagName": "span",
|
||||
"properties": {},
|
||||
"children": [],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
() => null,
|
||||
)).toEqual({
|
||||
"type": "root",
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"tagName": "div",
|
||||
"properties": {},
|
||||
"children": [],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle tx fxns that return: null, node ", () => {
|
||||
expect(transform).toBeDefined();
|
||||
|
||||
const html = '<div></div>';
|
||||
const ast = htmlToAst(html);
|
||||
|
||||
expect(transform(ast, () => null)).toEqual({
|
||||
"type": "root",
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"tagName": "div",
|
||||
"properties": {},
|
||||
"children": [],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle tx fxns that return: node, null ", () => {
|
||||
expect(transform).toBeDefined();
|
||||
|
||||
const html = '<div></div>';
|
||||
const ast = htmlToAst(html);
|
||||
|
||||
expect(transform(ast, () => null)).toEqual({
|
||||
"type": "root",
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"tagName": "div",
|
||||
"properties": {},
|
||||
"children": [],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle tx fxns that return: undefined, node ", () => {
|
||||
expect(transform).toBeDefined();
|
||||
|
||||
const html = '<div></div>';
|
||||
const ast = htmlToAst(html);
|
||||
|
||||
expect(transform(ast, () => null)).toEqual({
|
||||
"type": "root",
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"tagName": "div",
|
||||
"properties": {},
|
||||
"children": [],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle tx fxns that return: node, undefined", () => {
|
||||
expect(transform).toBeDefined();
|
||||
|
||||
const html = '<div></div>';
|
||||
const ast = htmlToAst(html);
|
||||
|
||||
expect(transform(ast, () => null)).toEqual({
|
||||
"type": "root",
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"tagName": "div",
|
||||
"properties": {},
|
||||
"children": [],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 12,
|
||||
"offset": 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import {
|
||||
cloneNode,
|
||||
createAstNode,
|
||||
isAstNode,
|
||||
} from '../src/util.mjs';
|
||||
|
||||
describe("cloneNode()", () => {
|
||||
|
||||
it("should import cloneNode()", () => {
|
||||
expect(cloneNode).toBeDefined();
|
||||
});
|
||||
|
||||
it("should clone a node", () => {
|
||||
const node = {
|
||||
type: "root",
|
||||
children: [
|
||||
{
|
||||
type: "element",
|
||||
tagName: "div",
|
||||
properties: {},
|
||||
children: [],
|
||||
position: {
|
||||
start: {
|
||||
line: 1,
|
||||
column: 1,
|
||||
offset: 0,
|
||||
},
|
||||
end: {
|
||||
line: 1,
|
||||
column: 12,
|
||||
offset: 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(cloneNode(node)).toEqual(node);
|
||||
expect(cloneNode(node) === node).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createAstNode()", () => {
|
||||
|
||||
it("should import createAstNode()", () => {
|
||||
expect(createAstNode).toBeDefined();
|
||||
})
|
||||
|
||||
it("should create a generic AST node with no arguments", () => {
|
||||
expect(createAstNode()).toEqual({
|
||||
type: undefined,
|
||||
value: undefined,
|
||||
children: [],
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
describe("isAstNode()", () => {
|
||||
|
||||
it("should import isAstNode()", () => {
|
||||
expect(isAstNode).toBeDefined();
|
||||
});
|
||||
|
||||
it("should return true if the node is an AST node", () => {
|
||||
[
|
||||
createAstNode(),
|
||||
].forEach(value => {
|
||||
expect(isAstNode(value)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return true if the node is an AST node", () => {
|
||||
[
|
||||
"string",
|
||||
"",
|
||||
null,
|
||||
undefined,
|
||||
{},
|
||||
{a:1},
|
||||
{children:[]},
|
||||
].forEach(value => {
|
||||
expect(isAstNode(value)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
Loading…
Reference in New Issue