diff --git a/compiler/generate/index.js b/compiler/generate/index.js
index f8fde71546..8cf5e9ad2f 100644
--- a/compiler/generate/index.js
+++ b/compiler/generate/index.js
@@ -1,5 +1,7 @@
-import deindent from 'deindent';
-import walkHtml from './walkHtml.js';
+import deindent from './utils/deindent.js';
+import walkHtml from './utils/walkHtml.js';
+
+const ROOT = 'options.target';
export default function generate ( parsed ) {
const counters = {
@@ -20,17 +22,28 @@ export default function generate ( parsed ) {
// create component
parsed.html.children.forEach( child => {
- let currentElement = 'options.target';
- const elementStack = [ currentElement ];
+ let current = {
+ target: ROOT,
+ indentation: 0,
+ block: []
+ };
+
+ const stack = [ current ];
walkHtml( child, {
enter ( node ) {
if ( node.type === 'Element' ) {
- currentElement = `element_${counters.element++}`;
+ current = {
+ target: `element_${counters.element++}`,
+ indentation: current.indentation,
+ block: current.block
+ };
+
+ stack.push( current );
const renderBlock = deindent`
- var ${currentElement} = document.createElement( '${node.name}' );
- options.target.appendChild( ${currentElement} );
+ var ${current.target} = document.createElement( '${node.name}' );
+ options.target.appendChild( ${current.target} );
`;
renderBlocks.push( renderBlock );
@@ -40,14 +53,51 @@ export default function generate ( parsed ) {
// ` );
teardownBlocks.push( deindent`
- ${currentElement}.parentNode.removeChild( ${currentElement} );
+ ${current.target}.parentNode.removeChild( ${current.target} );
` );
}
else if ( node.type === 'Text' ) {
+ if ( current.target === ROOT ) {
+ const identifier = `text_${counters.text++}`;
+
+ renderBlocks.push( deindent`
+ var ${identifier} = document.createTextNode( ${JSON.stringify( node.data )} );
+ ${current.target}.appendChild( ${identifier} );
+ ` );
+
+ teardownBlocks.push( deindent`
+ ${identifier}.parentNode.removeChild( ${identifier} );
+ ` );
+ }
+
+ else {
+ renderBlocks.push( deindent`
+ ${current.target}.appendChild( document.createTextNode( ${JSON.stringify( node.data )} ) );
+ ` );
+ }
+ }
+
+ else if ( node.type === 'MustacheTag' ) {
+ const identifier = `text_${counters.text++}`;
+ const expression = node.expression.type === 'Identifier' ? node.expression.name : 'TODO'; // TODO handle block-local state
+
renderBlocks.push( deindent`
- ${currentElement}.appendChild( document.createTextNode( ${JSON.stringify( node.data )} ) );
+ var ${identifier} = document.createTextNode( '' );
+ ${current.target}.appendChild( ${identifier} );
` );
+
+ updateBlocks.push( deindent`
+ if ( state.${expression} !== oldState.${expression} ) {
+ ${identifier}.data = state.${expression};
+ }
+ ` );
+
+ if ( current.target === ROOT ) {
+ teardownBlocks.push( deindent`
+ ${identifier}.parentNode.removeChild( ${identifier} );
+ ` );
+ }
}
else {
@@ -57,8 +107,8 @@ export default function generate ( parsed ) {
leave ( node ) {
if ( node.type === 'Element' ) {
- elementStack.pop();
- currentElement = elementStack[ elementStack.length - 1 ];
+ stack.pop();
+ current = stack[ stack.length - 1 ];
}
}
});
@@ -127,7 +177,7 @@ export default function generate ( parsed ) {
return component;
}
- `.trim();
+ `;
return { code };
}
diff --git a/compiler/generate/utils/__test__.js b/compiler/generate/utils/__test__.js
new file mode 100644
index 0000000000..68c9cc4829
--- /dev/null
+++ b/compiler/generate/utils/__test__.js
@@ -0,0 +1,38 @@
+import * as assert from 'assert';
+import deindent from './deindent.js';
+
+describe( 'utils', () => {
+ describe( 'deindent', () => {
+ it( 'deindents a simple string', () => {
+ const deindented = deindent`
+ deindent me please
+ `;
+
+ assert.equal( deindented, `deindent me please` );
+ });
+
+ it( 'deindents a multiline string', () => {
+ const deindented = deindent`
+ deindent me please
+ and me as well
+ `;
+
+ assert.equal( deindented, `deindent me please\nand me as well` );
+ });
+
+ it( 'preserves indentation of inserted values', () => {
+ const insert = deindent`
+ line one
+ line two
+ `;
+
+ const deindented = deindent`
+ before
+ ${insert}
+ after
+ `;
+
+ assert.equal( deindented, `before\n\tline one\n\tline two\nafter` );
+ });
+ });
+});
diff --git a/compiler/generate/utils/deindent.js b/compiler/generate/utils/deindent.js
new file mode 100644
index 0000000000..63730254b3
--- /dev/null
+++ b/compiler/generate/utils/deindent.js
@@ -0,0 +1,25 @@
+const start = /\n(\t+)/;
+
+export default function deindent ( strings, ...values ) {
+ const indentation = start.exec( strings[0] )[1];
+ const pattern = new RegExp( `^${indentation}`, 'gm' );
+
+ let result = strings[0].replace( start, '' ).replace( pattern, '' );
+
+ let trailingIndentation = getTrailingIndentation( result );
+
+ for ( let i = 1; i < strings.length; i += 1 ) {
+ const value = String( values[ i - 1 ] ).replace( /\n/g, `\n${trailingIndentation}` );
+ result += value + strings[i].replace( pattern, '' );
+
+ trailingIndentation = getTrailingIndentation( result );
+ }
+
+ return result.trim();
+}
+
+function getTrailingIndentation ( str ) {
+ let i = str.length;
+ while ( str[ i - 1 ] === ' ' || str[ i - 1 ] === '\t' ) i -= 1;
+ return str.slice( i, str.length );
+}
diff --git a/compiler/generate/walkHtml.js b/compiler/generate/utils/walkHtml.js
similarity index 100%
rename from compiler/generate/walkHtml.js
rename to compiler/generate/utils/walkHtml.js
diff --git a/compiler/index.js b/compiler/index.js
index 4a0199203c..6ed3caaf14 100644
--- a/compiler/index.js
+++ b/compiler/index.js
@@ -3,6 +3,7 @@ import generate from './generate/index.js';
export function compile ( template ) {
const parsed = parse( template );
+ // TODO validate template
const generated = generate( parsed );
return generated;
diff --git a/compiler/parse/__test__.js b/compiler/parse/__test__.js
index 7ba407bb49..6684e30fe6 100644
--- a/compiler/parse/__test__.js
+++ b/compiler/parse/__test__.js
@@ -108,4 +108,39 @@ describe( 'parse', () => {
js: null
});
});
+
+ it( 'parses an {{#if}}...{{/if}} block', () => {
+ const template = `{{#if foo}}bar{{/if}}`;
+
+ assert.deepEqual( parse( template ), {
+ html: {
+ start: 0,
+ end: 21,
+ type: 'Fragment',
+ children: [
+ {
+ start: 0,
+ end: 21,
+ type: 'IfBlock',
+ expression: {
+ start: 6,
+ end: 9,
+ type: 'Identifier',
+ name: 'foo'
+ },
+ children: [
+ {
+ start: 11,
+ end: 14,
+ type: 'Text',
+ data: 'bar'
+ }
+ ]
+ }
+ ]
+ },
+ css: null,
+ js: null
+ });
+ });
});
diff --git a/compiler/parse/index.js b/compiler/parse/index.js
index 59500fe388..2c07fe96ac 100644
--- a/compiler/parse/index.js
+++ b/compiler/parse/index.js
@@ -1,8 +1,6 @@
-import { parseExpressionAt } from 'acorn';
import { locate } from 'locate-character';
+import fragment from './state/fragment.js';
-const validTagName = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
-const voidElementNames = /^(?:area|base|br|col|command|doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
const whitespace = /\s/;
export default function parse ( template ) {
@@ -20,11 +18,15 @@ export default function parse ( template ) {
throw new Error( `${message} (${line}:${column})` );
},
- eat ( str ) {
+ eat ( str, required ) {
if ( this.match( str ) ) {
this.index += str.length;
return true;
}
+
+ if ( required ) {
+ this.error( `Expected ${str}` );
+ }
},
match ( str ) {
@@ -44,6 +46,14 @@ export default function parse ( template ) {
remaining () {
return this.template.slice( this.index );
+ },
+
+ requireWhitespace () {
+ if ( !whitespace.test( this.template[ this.index ] ) ) {
+ this.error( `Expected whitespace` );
+ }
+
+ this.allowWhitespace();
}
};
@@ -59,236 +69,10 @@ export default function parse ( template ) {
parser.stack.push( html );
- function fragment () {
- parser.allowWhitespace();
-
- if ( parser.match( '<' ) ) {
- return tag;
- }
-
- if ( parser.match( '{{' ) ) {
- return mustache;
- }
-
- return text;
- }
-
- function tag () {
- const start = parser.index++;
-
- const isClosingTag = parser.eat( '/' );
-
- // TODO handle cases like
onetwo
-
- const name = readTagName();
-
- if ( isClosingTag ) {
- if ( !parser.eat( '>' ) ) parser.error( `Expected '>'` );
-
- parser.current().end = parser.index;
- parser.stack.pop();
-
- return fragment;
- }
-
- const attributes = [];
-
- let attribute;
- while ( attribute = readAttribute() ) {
- attributes.push( attribute );
- }
-
- parser.allowWhitespace();
-
- const element = {
- start,
- end: null, // filled in later
- type: 'Element',
- name,
- attributes,
- children: []
- };
-
- parser.current().children.push( element );
-
- const selfClosing = parser.eat( '/' ) || voidElementNames.test( name );
-
- if ( !parser.eat( '>' ) ) {
- parser.error( `Expected >` );
- }
-
- if ( selfClosing ) {
- element.end = parser.index;
- } else {
- // don't push self-closing elements onto the stack
- parser.stack.push( element );
- }
-
- return fragment;
- }
-
- function readTagName () {
- const start = parser.index;
-
- const name = parser.readUntil( /(\s|\/|>)/ );
- if ( !validTagName.test( name ) ) {
- parser.error( `Expected valid tag name`, start );
- }
-
- return name;
- }
-
- function readAttribute () {
- const name = parser.readUntil( /(\s|=|\/|>)/ );
- if ( !name ) return null;
-
- parser.allowWhitespace();
-
- const value = parser.eat( '=' ) ? readAttributeValue() : true;
-
- return { name, value };
- }
-
- function readAttributeValue () {
- if ( parser.eat( `'` ) ) return readQuotedAttributeValue( `'` );
- if ( parser.eat( `"` ) ) return readQuotedAttributeValue( `"` );
-
- parser.error( `TODO unquoted attribute values` );
- }
-
- function readQuotedAttributeValue ( quoteMark ) {
- let currentChunk = {
- start: parser.index,
- end: null,
- type: 'AttributeText',
- data: ''
- };
-
- let escaped = false;
-
- const chunks = [];
-
- while ( parser.index < parser.template.length ) {
- if ( escaped ) {
- currentChunk.data += parser.template[ parser.index++ ];
- }
-
- else {
- if ( parser.match( '{{' ) ) {
- const index = parser.index;
- currentChunk.end = index;
-
- if ( currentChunk.data ) {
- chunks.push( currentChunk );
- }
-
- const expression = readExpression();
- parser.allowWhitespace();
- if ( !parser.eat( '}}' ) ) {
- parser.error( `Expected }}` );
- }
-
- chunks.push({
- start: index,
- end: parser.index,
- type: 'MustacheTag',
- expression
- });
-
- currentChunk = {
- start: parser.index,
- end: null,
- type: 'AttributeText',
- data: ''
- };
- }
-
- else if ( parser.match( '\\' ) ) {
- escaped = true;
- }
-
- else if ( parser.match( quoteMark ) ) {
- if ( currentChunk.data ) {
- chunks.push( currentChunk );
- return chunks;
- }
- }
- }
- }
-
- parser.error( `Unexpected end of input` );
- }
-
- function mustache () {
- const start = parser.index;
- parser.index += 2;
-
- parser.allowWhitespace();
-
- if ( parser.match( '#if' ) ) {
- return ifBlock;
- }
-
- if ( parser.match( '#each' ) ) {
- return eachBlock;
- }
-
- const expression = readExpression( template, parser.index );
-
- parser.allowWhitespace();
- if ( !parser.eat( '}}' ) ) parser.error( `Expected }}` );
-
- parser.current().children.push({
- start,
- end: parser.index,
- type: 'MustacheTag',
- expression
- });
-
- return fragment;
- }
-
- function ifBlock () {
- throw new Error( 'TODO' );
- }
-
- function eachBlock () {
- throw new Error( 'TODO' );
- }
-
- function readExpression () {
- const node = parseExpressionAt( parser.template, parser.index );
- parser.index = node.end;
-
- // TODO check it's a valid expression. probably shouldn't have
- // [arrow] function expressions, etc
-
- return node;
- }
-
- function text () {
- const start = parser.index;
-
- let data = '';
-
- while ( !parser.match( '<' ) && !parser.match( '{{' ) ) {
- data += template[ parser.index++ ];
- }
-
- parser.current().children.push({
- start,
- end: parser.index,
- type: 'Text',
- data
- });
-
- return fragment;
- }
-
let state = fragment;
while ( parser.index < parser.template.length ) {
- state = state();
+ state = state( parser ) || fragment;
}
return { html, css, js };
diff --git a/compiler/parse/read/expression.js b/compiler/parse/read/expression.js
new file mode 100644
index 0000000000..b67ab16e64
--- /dev/null
+++ b/compiler/parse/read/expression.js
@@ -0,0 +1,11 @@
+import { parseExpressionAt } from 'acorn';
+
+export default function readExpression ( parser ) {
+ const node = parseExpressionAt( parser.template, parser.index );
+ parser.index = node.end;
+
+ // TODO check it's a valid expression. probably shouldn't have
+ // [arrow] function expressions, etc
+
+ return node;
+}
diff --git a/compiler/parse/state/fragment.js b/compiler/parse/state/fragment.js
new file mode 100644
index 0000000000..59868a3270
--- /dev/null
+++ b/compiler/parse/state/fragment.js
@@ -0,0 +1,17 @@
+import tag from './tag.js';
+import mustache from './mustache.js';
+import text from './text.js';
+
+export default function fragment ( parser ) {
+ parser.allowWhitespace();
+
+ if ( parser.match( '<' ) ) {
+ return tag;
+ }
+
+ if ( parser.match( '{{' ) ) {
+ return mustache;
+ }
+
+ return text;
+}
diff --git a/compiler/parse/state/mustache.js b/compiler/parse/state/mustache.js
new file mode 100644
index 0000000000..b3a1860047
--- /dev/null
+++ b/compiler/parse/state/mustache.js
@@ -0,0 +1,78 @@
+import readExpression from '../read/expression.js';
+
+export default function mustache ( parser ) {
+ const start = parser.index;
+ parser.index += 2;
+
+ parser.allowWhitespace();
+
+ // {{/if}} or {{/each}}
+ if ( parser.eat( '/' ) ) {
+ const current = parser.current();
+ let expected;
+
+ if ( current.type === 'IfBlock' ) {
+ expected = 'if';
+ } else if ( current.type === 'EachBlock' ) {
+ expected = 'each';
+ } else {
+ parser.error( `Unexpected block closing tag` );
+ }
+
+ parser.eat( expected, true );
+ parser.allowWhitespace();
+ parser.eat( '}}', true );
+
+ current.end = parser.index;
+ parser.stack.pop();
+ }
+
+ // TODO {{else}} and {{elseif expression}}
+
+ // {{#if foo}} or {{#each foo}}
+ else if ( parser.eat( '#' ) ) {
+ let type;
+
+ if ( parser.eat( 'if' ) ) {
+ type = 'IfBlock';
+ } else if ( parser.eat( 'each' ) ) {
+ type = 'EachBlock';
+ } else {
+ parser.error( `Expected if or each` );
+ }
+
+ parser.requireWhitespace();
+
+ const expression = readExpression( parser );
+
+ parser.allowWhitespace();
+ parser.eat( '}}', true );
+
+ const block = {
+ start,
+ end: null,
+ type,
+ expression,
+ children: []
+ };
+
+ parser.current().children.push( block );
+ parser.stack.push( block );
+ }
+
+ else {
+ const expression = readExpression( parser );
+
+ parser.allowWhitespace();
+ parser.eat( '}}', true );
+
+ parser.current().children.push({
+ start,
+ end: parser.index,
+ type: 'MustacheTag',
+ expression
+ });
+ }
+
+ return null;
+}
diff --git a/compiler/parse/state/tag.js b/compiler/parse/state/tag.js
new file mode 100644
index 0000000000..e0ee38d7a7
--- /dev/null
+++ b/compiler/parse/state/tag.js
@@ -0,0 +1,150 @@
+import readExpression from '../read/expression.js';
+
+const validTagName = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
+const voidElementNames = /^(?:area|base|br|col|command|doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
+
+export default function tag ( parser ) {
+ const start = parser.index++;
+
+ const isClosingTag = parser.eat( '/' );
+
+ // TODO handle cases like onetwo
+
+ const name = readTagName( parser );
+
+ if ( isClosingTag ) {
+ if ( !parser.eat( '>' ) ) parser.error( `Expected '>'` );
+
+ parser.current().end = parser.index;
+ parser.stack.pop();
+
+ return null;
+ }
+
+ const attributes = [];
+
+ let attribute;
+ while ( attribute = readAttribute( parser ) ) {
+ attributes.push( attribute );
+ }
+
+ parser.allowWhitespace();
+
+ const element = {
+ start,
+ end: null, // filled in later
+ type: 'Element',
+ name,
+ attributes,
+ children: []
+ };
+
+ parser.current().children.push( element );
+
+ const selfClosing = parser.eat( '/' ) || voidElementNames.test( name );
+
+ if ( !parser.eat( '>' ) ) {
+ parser.error( `Expected >` );
+ }
+
+ if ( selfClosing ) {
+ element.end = parser.index;
+ } else {
+ // don't push self-closing elements onto the stack
+ parser.stack.push( element );
+ }
+
+ return null;
+}
+
+function readTagName ( parser ) {
+ const start = parser.index;
+
+ const name = parser.readUntil( /(\s|\/|>)/ );
+ if ( !validTagName.test( name ) ) {
+ parser.error( `Expected valid tag name`, start );
+ }
+
+ return name;
+}
+
+function readAttribute ( parser ) {
+ const name = parser.readUntil( /(\s|=|\/|>)/ );
+ if ( !name ) return null;
+
+ parser.allowWhitespace();
+
+ const value = parser.eat( '=' ) ? readAttributeValue( parser ) : true;
+
+ return { name, value };
+}
+
+function readAttributeValue ( parser ) {
+ if ( parser.eat( `'` ) ) return readQuotedAttributeValue( parser, `'` );
+ if ( parser.eat( `"` ) ) return readQuotedAttributeValue( parser, `"` );
+
+ parser.error( `TODO unquoted attribute values` );
+}
+
+function readQuotedAttributeValue ( parser, quoteMark ) {
+ let currentChunk = {
+ start: parser.index,
+ end: null,
+ type: 'AttributeText',
+ data: ''
+ };
+
+ let escaped = false;
+
+ const chunks = [];
+
+ while ( parser.index < parser.template.length ) {
+ if ( escaped ) {
+ currentChunk.data += parser.template[ parser.index++ ];
+ }
+
+ else {
+ if ( parser.match( '{{' ) ) {
+ const index = parser.index;
+ currentChunk.end = index;
+
+ if ( currentChunk.data ) {
+ chunks.push( currentChunk );
+ }
+
+ const expression = readExpression();
+ parser.allowWhitespace();
+ if ( !parser.eat( '}}' ) ) {
+ parser.error( `Expected }}` );
+ }
+
+ chunks.push({
+ start: index,
+ end: parser.index,
+ type: 'MustacheTag',
+ expression
+ });
+
+ currentChunk = {
+ start: parser.index,
+ end: null,
+ type: 'AttributeText',
+ data: ''
+ };
+ }
+
+ else if ( parser.match( '\\' ) ) {
+ escaped = true;
+ }
+
+ else if ( parser.match( quoteMark ) ) {
+ if ( currentChunk.data ) {
+ chunks.push( currentChunk );
+ return chunks;
+ }
+ }
+ }
+ }
+
+ parser.error( `Unexpected end of input` );
+}
diff --git a/compiler/parse/state/text.js b/compiler/parse/state/text.js
new file mode 100644
index 0000000000..51ccffa74d
--- /dev/null
+++ b/compiler/parse/state/text.js
@@ -0,0 +1,18 @@
+export default function text ( parser ) {
+ const start = parser.index;
+
+ let data = '';
+
+ while ( !parser.match( '<' ) && !parser.match( '{{' ) ) {
+ data += parser.template[ parser.index++ ];
+ }
+
+ parser.current().children.push({
+ start,
+ end: parser.index,
+ type: 'Text',
+ data
+ });
+
+ return null;
+}
diff --git a/package.json b/package.json
index a2d139819c..e70d65d3da 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,6 @@
},
"dependencies": {
"acorn": "^4.0.3",
- "deindent": "^0.1.0",
"locate-character": "^2.0.0"
}
}
diff --git a/test/samples/hello-world/_config.js b/test/samples/hello-world/_config.js
new file mode 100644
index 0000000000..6c5815a9fd
--- /dev/null
+++ b/test/samples/hello-world/_config.js
@@ -0,0 +1,16 @@
+import * as assert from 'assert';
+
+export default {
+ description: 'hello world',
+ data: {
+ name: 'world'
+ },
+ html: 'Hello world!
',
+
+ test ( component, target ) {
+ component.set({ name: 'everybody' });
+ assert.equal( target.innerHTML, 'Hello everybody!
' );
+ component.teardown();
+ assert.equal( target.innerHTML, '' );
+ }
+};
diff --git a/test/samples/hello-world/main.svelte b/test/samples/hello-world/main.svelte
new file mode 100644
index 0000000000..f674269464
--- /dev/null
+++ b/test/samples/hello-world/main.svelte
@@ -0,0 +1 @@
+Hello {{name}}!
diff --git a/test/samples/if-block/_config.js b/test/samples/if-block/_config.js
new file mode 100644
index 0000000000..8d7f589d68
--- /dev/null
+++ b/test/samples/if-block/_config.js
@@ -0,0 +1,7 @@
+export default {
+ description: '{{#if}}...{{/if}} block',
+ data: {
+ visible: true
+ },
+ html: 'i am visible
'
+};
diff --git a/test/samples/if-block/main.svelte b/test/samples/if-block/main.svelte
new file mode 100644
index 0000000000..5150cf0330
--- /dev/null
+++ b/test/samples/if-block/main.svelte
@@ -0,0 +1,3 @@
+{{#if visible}}
+ i am visible
+{{/if}}
diff --git a/test/test.js b/test/test.js
index 7fe4356ccd..57dc03ba76 100644
--- a/test/test.js
+++ b/test/test.js
@@ -1,11 +1,14 @@
import { compile } from '../compiler/index.js';
import * as assert from 'assert';
+import * as path from 'path';
import * as fs from 'fs';
import jsdom from 'jsdom';
+const cache = {};
+
require.extensions[ '.svelte' ] = function ( module, filename ) {
- const source = fs.readFileSync( filename, 'utf-8' );
- const { code } = compile( source );
+ const code = cache[ filename ];
+ if ( !code ) throw new Error( `not compiled: ${filename}` );
return module._compile( code, filename );
};
@@ -39,22 +42,53 @@ describe( 'svelte', () => {
fs.readdirSync( 'test/samples' ).forEach( dir => {
if ( dir[0] === '.' ) return;
- it( dir, () => {
- const config = loadConfig( dir );
+ const config = loadConfig( dir );
+
+ ( config.solo ? it.only : it )( dir, () => {
+ let compiled;
+
+ try {
+ const source = fs.readFileSync( `test/samples/${dir}/main.svelte`, 'utf-8' );
+ compiled = compile( source );
+ } catch ( err ) {
+ if ( config.compileError ) {
+ config.compileError( err );
+ return;
+ } else {
+ throw err;
+ }
+ }
+
+ const { code } = compiled;
+
+ cache[ path.resolve( `test/samples/${dir}/main.svelte` ) ] = code;
const factory = require( `./samples/${dir}/main.svelte` ).default;
- return env().then( window => {
- const target = window.document.querySelector( 'main' );
+ if ( config.show ) {
+ console.log( code ); // eslint-disable-line no-console
+ }
- const component = factory({
- target,
- data: config.data
- });
+ return env()
+ .then( window => {
+ const target = window.document.querySelector( 'main' );
- if ( config.html ) {
- assert.equal( target.innerHTML, config.html );
- }
- });
+ const component = factory({
+ target,
+ data: config.data
+ });
+
+ if ( config.html ) {
+ assert.equal( target.innerHTML, config.html );
+ }
+
+ if ( config.test ) {
+ config.test( component, target );
+ }
+ })
+ .catch( err => {
+ if ( !config.show ) console.log( code ); // eslint-disable-line no-console
+ throw err;
+ });
});
});
});