diff --git a/mocha.coverage.opts b/mocha.coverage.opts index 44c841b78e..5ad6a0e7a4 100644 --- a/mocha.coverage.opts +++ b/mocha.coverage.opts @@ -2,4 +2,4 @@ --require reify --recursive ./**/__test__.js -test/test.js +test/*.js diff --git a/mocha.opts b/mocha.opts index 5b1cf3bcb3..d10eaa31b3 100644 --- a/mocha.opts +++ b/mocha.opts @@ -1,4 +1,4 @@ --require reify --recursive ./**/__test__.js -test/test.js +test/*.js diff --git a/package.json b/package.json index 0cfa078cb0..41de5e2370 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "coverage": "nyc report --reporter=text-lcov > coverage.lcov", "codecov": "codecov", "precodecov": "npm run coverage", - "lint": "eslint src", + "lint": "eslint src test/*.js", "build": "npm run build:main && npm run build:ssr", "build:main": "rollup -c rollup.config.main.js", "build:ssr": "rollup -c rollup.config.ssr.js", @@ -66,10 +66,10 @@ }, "nyc": { "include": [ - "compiler/**/*.js" + "src/**/*.js" ], "exclude": [ - "compiler/**/__test__.js" + "src/**/__test__.js" ] }, "babel": { diff --git a/rollup.config.ssr.js b/rollup.config.ssr.js index dd15e6592f..912aaecc3f 100644 --- a/rollup.config.ssr.js +++ b/rollup.config.ssr.js @@ -1,3 +1,4 @@ +import * as path from 'path'; import nodeResolve from 'rollup-plugin-node-resolve'; import commonjs from 'rollup-plugin-commonjs'; @@ -11,9 +12,9 @@ export default { nodeResolve({ jsnext: true, module: true }), commonjs() ], - external: [ 'svelte', 'magic-string' ], + external: [ path.resolve( 'src/index.js' ), 'magic-string' ], paths: { - svelte: '../compiler/svelte.js' + [ path.resolve( 'src/index.js' ) ]: '../compiler/svelte.js' }, sourceMap: true }; diff --git a/src/server-side-rendering/compile.js b/src/server-side-rendering/compile.js index 346e40ce75..38fdab5502 100644 --- a/src/server-side-rendering/compile.js +++ b/src/server-side-rendering/compile.js @@ -1,4 +1,4 @@ -import { parse, validate } from 'svelte'; +import { parse, validate } from '../index.js'; import { walk } from 'estree-walker'; import deindent from '../utils/deindent.js'; import isReference from '../utils/isReference.js'; diff --git a/test/formats.js b/test/formats.js new file mode 100644 index 0000000000..a712c4b7a4 --- /dev/null +++ b/test/formats.js @@ -0,0 +1,178 @@ +import deindent from '../src/utils/deindent.js'; +import assert from 'assert'; +import { svelte, env, setupHtmlEqual } from './helpers.js'; + +function testAmd ( code, expectedId, dependencies, html ) { + const fn = new Function( 'define', code ); + + return env().then( window => { + function define ( id, deps, factory ) { + assert.equal( id, expectedId ); + assert.deepEqual( deps, Object.keys( dependencies ) ); + + const SvelteComponent = factory( ...Object.keys( dependencies ).map( key => dependencies[ key ] ) ); + + const main = window.document.body.querySelector( 'main' ); + const component = new SvelteComponent({ target: main }); + + assert.htmlEqual( main.innerHTML, html ); + + component.teardown(); + } + + define.amd = true; + + fn( define ); + }); +} + +function testCjs ( code, dependencyById, html ) { + const fn = new Function( 'module', 'exports', 'require', code ); + + return env().then( window => { + const module = { exports: {} }; + const require = id => { + return dependencyById[ id ]; + }; + + fn( module, module.exports, require ); + + const SvelteComponent = module.exports; + + const main = window.document.body.querySelector( 'main' ); + const component = new SvelteComponent({ target: main }); + + assert.htmlEqual( main.innerHTML, html ); + + component.teardown(); + }); +} + +function testIife ( code, name, globals, html ) { + const fn = new Function( Object.keys( globals ), `${code}\n\nreturn ${name};` ); + + return env().then( window => { + const SvelteComponent = fn( ...Object.keys( globals ).map( key => globals[ key ] ) ); + + const main = window.document.body.querySelector( 'main' ); + const component = new SvelteComponent({ target: main }); + + assert.htmlEqual( main.innerHTML, html ); + + component.teardown(); + }); +} + +describe( 'formats', () => { + before( setupHtmlEqual ); + + describe( 'amd', () => { + it( 'generates an AMD module', () => { + const source = deindent` +
{{answer}}
+ + + `; + + const { code } = svelte.compile( source, { + format: 'amd', + amd: { id: 'foo' } + }); + + return testAmd( code, 'foo', { answer: 42 }, `
42
` ); + }); + }); + + describe( 'cjs', () => { + it( 'generates a CommonJS module', () => { + const source = deindent` +
{{answer}}
+ + + `; + + const { code } = svelte.compile( source, { + format: 'cjs' + }); + + return testCjs( code, { answer: 42 }, `
42
` ); + }); + }); + + describe( 'iife', () => { + it( 'generates a self-executing script', () => { + const source = deindent` +
{{answer}}
+ + + `; + + const { code } = svelte.compile( source, { + format: 'iife', + name: 'Foo', + globals: { + answer: 'answer' + } + }); + + return testIife( code, 'Foo', { answer: 42 }, `
42
` ); + }); + }); + + describe( 'umd', () => { + it( 'generates a UMD build', () => { + const source = deindent` +
{{answer}}
+ + + `; + + const { code } = svelte.compile( source, { + format: 'umd', + name: 'Foo', + globals: { + answer: 'answer' + }, + amd: { + id: 'foo' + } + }); + + return testAmd( code, 'foo', { answer: 42 }, `
42
` ) + .then( () => testCjs( code, { answer: 42 }, `
42
` ) ) + .then( () => testIife( code, 'Foo', { answer: 42 }, `
42
` ) ); + }); + }); +}); diff --git a/test/generate.js b/test/generate.js new file mode 100644 index 0000000000..2c996fd1ca --- /dev/null +++ b/test/generate.js @@ -0,0 +1,121 @@ +import spaces from '../src/utils/spaces.js'; +import assert from 'assert'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as acorn from 'acorn'; + +import { svelte, env, setupHtmlEqual } from './helpers.js'; + +const cache = {}; + +let showCompiledCode = false; +let compileOptions = null; + +require.extensions[ '.html' ] = function ( module, filename ) { + const options = Object.assign({ filename }, compileOptions ); + const code = cache[ filename ] || ( cache[ filename ] = svelte.compile( fs.readFileSync( filename, 'utf-8' ), options ).code ); + if ( showCompiledCode ) console.log( addLineNumbers( code ) ); // eslint-disable-line no-console + + return module._compile( code, filename ); +}; + +function addLineNumbers ( code ) { + return code.split( '\n' ).map( ( line, i ) => { + i = String( i + 1 ); + while ( i.length < 3 ) i = ` ${i}`; + + return `${i}: ${line.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) )}`; + }).join( '\n' ); +} + +function loadConfig ( dir ) { + try { + return require( `./generator/${dir}/_config.js` ).default; + } catch ( err ) { + if ( err.code === 'E_NOT_FOUND' ) { + return {}; + } + + throw err; + } +} + +describe( 'generate', () => { + before( setupHtmlEqual ); + + fs.readdirSync( 'test/generator' ).forEach( dir => { + if ( dir[0] === '.' ) return; + + const config = loadConfig( dir ); + + ( config.skip ? it.skip : config.solo ? it.only : it )( dir, () => { + let compiled; + + showCompiledCode = config.show; + compileOptions = config.compileOptions || {}; + + try { + const source = fs.readFileSync( `test/generator/${dir}/main.html`, 'utf-8' ); + compiled = svelte.compile( source ); + } catch ( err ) { + if ( config.compileError ) { + config.compileError( err ); + return; + } else { + throw err; + } + } + + const { code } = compiled; + + // check that no ES2015+ syntax slipped in + try { + const startIndex = code.indexOf( 'function renderMainFragment' ); // may change! + const es5 = spaces( startIndex ) + code.slice( startIndex ).replace( /export default .+/, '' ); + acorn.parse( es5, { ecmaVersion: 5 }); + } catch ( err ) { + if ( !config.show ) console.log( addLineNumbers( code ) ); // eslint-disable-line no-console + throw err; + } + + cache[ path.resolve( `test/generator/${dir}/main.html` ) ] = code; + + let SvelteComponent; + + try { + SvelteComponent = require( `./generator/${dir}/main.html` ).default; + } catch ( err ) { + if ( !config.show ) console.log( addLineNumbers( code ) ); // eslint-disable-line no-console + throw err; + } + + return env() + .then( window => { + // Put the constructor on window for testing + window.SvelteComponent = SvelteComponent; + + const target = window.document.querySelector( 'main' ); + + const component = new SvelteComponent({ + target, + data: config.data + }); + + if ( config.html ) { + assert.htmlEqual( target.innerHTML, config.html ); + } + + if ( config.test ) { + config.test( assert, component, target, window ); + } else { + component.teardown(); + assert.equal( target.innerHTML, '' ); + } + }) + .catch( err => { + if ( !config.show ) console.log( addLineNumbers( code ) ); // eslint-disable-line no-console + throw err; + }); + }); + }); +}); diff --git a/test/helpers.js b/test/helpers.js new file mode 100644 index 0000000000..d1f0539a6c --- /dev/null +++ b/test/helpers.js @@ -0,0 +1,112 @@ +import jsdom from 'jsdom'; +import assert from 'assert'; +import * as fs from 'fs'; + +import * as consoleGroup from 'console-group'; +consoleGroup.install(); + +import * as sourceMapSupport from 'source-map-support'; +sourceMapSupport.install(); + +// for coverage purposes, we need to test source files, +// but for sanity purposes, we need to test dist files +export const svelte = process.env.COVERAGE ? + require( '../src/index.js' ) : + require( '../compiler/svelte.js' ); + +export function exists ( path ) { + try { + fs.statSync( path ); + return true; + } catch ( err ) { + return false; + } +} + +export function tryToLoadJson ( file ) { + try { + return JSON.parse( fs.readFileSync( file ) ); + } catch ( err ) { + if ( err.code !== 'ENOENT' ) throw err; + return null; + } +} + +export function tryToReadFile ( file ) { + try { + return fs.readFileSync( file, 'utf-8' ); + } catch ( err ) { + if ( err.code !== 'ENOENT' ) throw err; + return null; + } +} + +export function env () { + return new Promise( ( fulfil, reject ) => { + jsdom.env( '
', ( err, window ) => { + if ( err ) { + reject( err ); + } else { + global.document = window.document; + fulfil( window ); + } + }); + }); +} + +function cleanChildren ( node ) { + let previous = null; + + [ ...node.childNodes ].forEach( child => { + if ( child.nodeType === 8 ) { + // comment + node.removeChild( child ); + return; + } + + if ( child.nodeType === 3 ) { + child.data = child.data.replace( /\s{2,}/, '\n' ); + + // text + if ( previous && previous.nodeType === 3 ) { + previous.data += child.data; + previous.data = previous.data.replace( /\s{2,}/, '\n' ); + + node.removeChild( child ); + } + } + + else { + cleanChildren( child ); + } + + previous = child; + }); + + // collapse whitespace + if ( node.firstChild && node.firstChild.nodeType === 3 ) { + node.firstChild.data = node.firstChild.data.replace( /^\s+/, '' ); + if ( !node.firstChild.data ) node.removeChild( node.firstChild ); + } + + if ( node.lastChild && node.lastChild.nodeType === 3 ) { + node.lastChild.data = node.lastChild.data.replace( /\s+$/, '' ); + if ( !node.lastChild.data ) node.removeChild( node.lastChild ); + } +} + +export function setupHtmlEqual () { + return env().then( window => { + assert.htmlEqual = ( actual, expected, message ) => { + window.document.body.innerHTML = actual.trim(); + cleanChildren( window.document.body, '' ); + actual = window.document.body.innerHTML; + + window.document.body.innerHTML = expected.trim(); + cleanChildren( window.document.body, '' ); + expected = window.document.body.innerHTML; + + assert.deepEqual( actual, expected, message ); + }; + }); +} diff --git a/test/parse.js b/test/parse.js new file mode 100644 index 0000000000..0be35e08f9 --- /dev/null +++ b/test/parse.js @@ -0,0 +1,36 @@ +import assert from 'assert'; +import * as fs from 'fs'; +import { svelte, exists } from './helpers.js'; + +describe( 'parse', () => { + fs.readdirSync( 'test/parser' ).forEach( dir => { + if ( dir[0] === '.' ) return; + + const solo = exists( `test/parser/${dir}/solo` ); + + ( solo ? it.only : it )( dir, () => { + const input = fs.readFileSync( `test/parser/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' ); + + try { + const actual = svelte.parse( input ); + const expected = require( `./parser/${dir}/output.json` ); + + assert.deepEqual( actual.html, expected.html ); + assert.deepEqual( actual.css, expected.css ); + assert.deepEqual( actual.js, expected.js ); + } catch ( err ) { + if ( err.name !== 'ParseError' ) throw err; + + try { + const expected = require( `./parser/${dir}/error.json` ); + + assert.equal( err.message, expected.message ); + assert.deepEqual( err.loc, expected.loc ); + assert.equal( err.pos, expected.pos ); + } catch ( err2 ) { + throw err2.code === 'MODULE_NOT_FOUND' ? err : err2; + } + } + }); + }); +}); diff --git a/test/sourcemaps.js b/test/sourcemaps.js new file mode 100644 index 0000000000..3417681629 --- /dev/null +++ b/test/sourcemaps.js @@ -0,0 +1,30 @@ +import * as fs from 'fs'; +import assert from 'assert'; +import { svelte, exists } from './helpers.js'; +import { SourceMapConsumer } from 'source-map'; +import { getLocator } from 'locate-character'; + +describe( 'sourcemaps', () => { + fs.readdirSync( 'test/sourcemaps' ).forEach( dir => { + if ( dir[0] === '.' ) return; + + const solo = exists( `test/sourcemaps/${dir}/solo` ); + + ( solo ? it.only : it )( dir, () => { + const input = fs.readFileSync( `test/sourcemaps/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' ); + const { code, map } = svelte.compile( input ); + + fs.writeFileSync( `test/sourcemaps/${dir}/output.js`, `${code}\n//# sourceMappingURL=output.js.map` ); + fs.writeFileSync( `test/sourcemaps/${dir}/output.js.map`, JSON.stringify( map, null, ' ' ) ); + + const { test } = require( `./sourcemaps/${dir}/test.js` ); + + const smc = new SourceMapConsumer( map ); + + const locateInSource = getLocator( input ); + const locateInGenerated = getLocator( code ); + + test({ assert, code, map, smc, locateInSource, locateInGenerated }); + }); + }); +}); diff --git a/test/ssr.js b/test/ssr.js new file mode 100644 index 0000000000..b985625922 --- /dev/null +++ b/test/ssr.js @@ -0,0 +1,44 @@ +import assert from 'assert'; +import * as fs from 'fs'; + +import { exists, tryToLoadJson } from './helpers.js'; + +function tryToReadFile ( file ) { + try { + return fs.readFileSync( file, 'utf-8' ); + } catch ( err ) { + if ( err.code !== 'ENOENT' ) throw err; + return null; + } +} + +describe( 'ssr', () => { + before( () => { + require( process.env.COVERAGE ? + '../src/server-side-rendering/register.js' : + '../ssr/register' ); + }); + + fs.readdirSync( 'test/server-side-rendering' ).forEach( dir => { + if ( dir[0] === '.' ) return; + + const solo = exists( `test/server-side-rendering/${dir}/solo` ); + + ( solo ? it.only : it )( dir, () => { + const component = require( `./server-side-rendering/${dir}/main.html` ); + + const expectedHtml = tryToReadFile( `test/server-side-rendering/${dir}/_expected.html` ); + const expectedCss = tryToReadFile( `test/server-side-rendering/${dir}/_expected.css` ) || ''; + + const data = tryToLoadJson( `test/server-side-rendering/${dir}/data.json` ); + const html = component.render( data ); + const { css } = component.renderCss(); + + fs.writeFileSync( `test/server-side-rendering/${dir}/_actual.html`, html ); + if ( css ) fs.writeFileSync( `test/server-side-rendering/${dir}/_actual.css`, css ); + + assert.htmlEqual( html, expectedHtml ); + assert.equal( css.replace( /^\s+/gm, '' ), expectedCss.replace( /^\s+/gm, '' ) ); + }); + }); +}); diff --git a/test/test.js b/test/test.js deleted file mode 100644 index b441265a32..0000000000 --- a/test/test.js +++ /dev/null @@ -1,549 +0,0 @@ -import deindent from '../src/utils/deindent.js'; -import spaces from '../src/utils/spaces.js'; -import assert from 'assert'; -import * as path from 'path'; -import * as fs from 'fs'; -import jsdom from 'jsdom'; -import * as acorn from 'acorn'; -import { SourceMapConsumer } from 'source-map'; -import { getLocator } from 'locate-character'; - -import * as consoleGroup from 'console-group'; -consoleGroup.install(); - -import * as sourceMapSupport from 'source-map-support'; -sourceMapSupport.install(); - -// for coverage purposes, we need to test source files, -// but for sanity purposes, we need to test dist files -const svelte = process.env.COVERAGE ? - require( '../src/index.js' ) : - require( '../compiler/svelte.js' ); - -const cache = {}; - -let showCompiledCode = false; -let compileOptions = null; - -require.extensions[ '.html' ] = function ( module, filename ) { - const options = Object.assign({ filename }, compileOptions ); - const code = cache[ filename ] || ( cache[ filename ] = svelte.compile( fs.readFileSync( filename, 'utf-8' ), options ).code ); - if ( showCompiledCode ) console.log( addLineNumbers( code ) ); // eslint-disable-line no-console - - return module._compile( code, filename ); -}; - -function exists ( path ) { - try { - fs.statSync( path ); - return true; - } catch ( err ) { - return false; - } -} - -function env () { - return new Promise( ( fulfil, reject ) => { - jsdom.env( '
', ( err, window ) => { - if ( err ) { - reject( err ); - } else { - global.document = window.document; - fulfil( window ); - } - }); - }); -} - -function addLineNumbers ( code ) { - return code.split( '\n' ).map( ( line, i ) => { - i = String( i + 1 ); - while ( i.length < 3 ) i = ` ${i}`; - - return `${i}: ${line.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) )}`; - }).join( '\n' ); -} - -function tryToLoadJson ( file ) { - try { - return JSON.parse( fs.readFileSync( file ) ); - } catch ( err ) { - if ( err.code !== 'ENOENT' ) throw err; - return null; - } -} - -function tryToReadFile ( file ) { - try { - return fs.readFileSync( file, 'utf-8' ); - } catch ( err ) { - if ( err.code !== 'ENOENT' ) throw err; - return null; - } -} - -describe( 'svelte', () => { - before( () => { - function cleanChildren ( node ) { - let previous = null; - - [ ...node.childNodes ].forEach( child => { - if ( child.nodeType === 8 ) { - // comment - node.removeChild( child ); - return; - } - - if ( child.nodeType === 3 ) { - child.data = child.data.replace( /\s{2,}/, '\n' ); - - // text - if ( previous && previous.nodeType === 3 ) { - previous.data += child.data; - previous.data = previous.data.replace( /\s{2,}/, '\n' ); - - node.removeChild( child ); - } - } - - else { - cleanChildren( child ); - } - - previous = child; - }); - - // collapse whitespace - if ( node.firstChild && node.firstChild.nodeType === 3 ) { - node.firstChild.data = node.firstChild.data.replace( /^\s+/, '' ); - if ( !node.firstChild.data ) node.removeChild( node.firstChild ); - } - - if ( node.lastChild && node.lastChild.nodeType === 3 ) { - node.lastChild.data = node.lastChild.data.replace( /\s+$/, '' ); - if ( !node.lastChild.data ) node.removeChild( node.lastChild ); - } - } - - return env().then( window => { - assert.htmlEqual = ( actual, expected, message ) => { - window.document.body.innerHTML = actual.trim(); - cleanChildren( window.document.body, '' ); - actual = window.document.body.innerHTML; - - window.document.body.innerHTML = expected.trim(); - cleanChildren( window.document.body, '' ); - expected = window.document.body.innerHTML; - - assert.deepEqual( actual, expected, message ); - }; - }); - }); - - describe( 'parse', () => { - fs.readdirSync( 'test/parser' ).forEach( dir => { - if ( dir[0] === '.' ) return; - - const solo = exists( `test/parser/${dir}/solo` ); - - ( solo ? it.only : it )( dir, () => { - const input = fs.readFileSync( `test/parser/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' ); - - try { - const actual = svelte.parse( input ); - const expected = require( `./parser/${dir}/output.json` ); - - assert.deepEqual( actual.html, expected.html ); - assert.deepEqual( actual.css, expected.css ); - assert.deepEqual( actual.js, expected.js ); - } catch ( err ) { - if ( err.name !== 'ParseError' ) throw err; - - try { - const expected = require( `./parser/${dir}/error.json` ); - - assert.equal( err.message, expected.message ); - assert.deepEqual( err.loc, expected.loc ); - assert.equal( err.pos, expected.pos ); - } catch ( err2 ) { - throw err2.code === 'MODULE_NOT_FOUND' ? err : err2; - } - } - }); - }); - }); - - describe( 'validate', () => { - fs.readdirSync( 'test/validator' ).forEach( dir => { - if ( dir[0] === '.' ) return; - - const solo = exists( `test/validator/${dir}/solo` ); - - ( solo ? it.only : it )( dir, () => { - const input = fs.readFileSync( `test/validator/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' ); - - try { - const parsed = svelte.parse( input ); - - const errors = []; - const warnings = []; - - const { names } = svelte.validate( parsed, input, { - onerror ( error ) { - errors.push({ - message: error.message, - pos: error.pos, - loc: error.loc - }); - }, - - onwarn ( warning ) { - warnings.push({ - message: warning.message, - pos: warning.pos, - loc: warning.loc - }); - } - }); - - const expectedErrors = tryToLoadJson( `test/validator/${dir}/errors.json` ) || []; - const expectedWarnings = tryToLoadJson( `test/validator/${dir}/warnings.json` ) || []; - const expectedNames = tryToLoadJson( `test/validator/${dir}/names.json` ) || []; - - assert.deepEqual( errors, expectedErrors ); - assert.deepEqual( warnings, expectedWarnings ); - assert.deepEqual( names, expectedNames ); - } catch ( err ) { - if ( err.name !== 'ParseError' ) throw err; - - try { - const expected = require( `./validator/${dir}/error.json` ); - - assert.equal( err.shortMessage, expected.message ); - assert.deepEqual( err.loc, expected.loc ); - assert.equal( err.pos, expected.pos ); - } catch ( err2 ) { - throw err2.code === 'MODULE_NOT_FOUND' ? err : err2; - } - } - }); - }); - }); - - describe( 'generate', () => { - function loadConfig ( dir ) { - try { - return require( `./generator/${dir}/_config.js` ).default; - } catch ( err ) { - if ( err.code === 'E_NOT_FOUND' ) { - return {}; - } - - throw err; - } - } - - fs.readdirSync( 'test/generator' ).forEach( dir => { - if ( dir[0] === '.' ) return; - - const config = loadConfig( dir ); - - ( config.skip ? it.skip : config.solo ? it.only : it )( dir, () => { - let compiled; - - showCompiledCode = config.show; - compileOptions = config.compileOptions || {}; - - try { - const source = fs.readFileSync( `test/generator/${dir}/main.html`, 'utf-8' ); - compiled = svelte.compile( source ); - } catch ( err ) { - if ( config.compileError ) { - config.compileError( err ); - return; - } else { - throw err; - } - } - - const { code } = compiled; - - // check that no ES2015+ syntax slipped in - try { - const startIndex = code.indexOf( 'function renderMainFragment' ); // may change! - const es5 = spaces( startIndex ) + code.slice( startIndex ).replace( /export default .+/, '' ); - acorn.parse( es5, { ecmaVersion: 5 }); - } catch ( err ) { - if ( !config.show ) console.log( addLineNumbers( code ) ); // eslint-disable-line no-console - throw err; - } - - cache[ path.resolve( `test/generator/${dir}/main.html` ) ] = code; - - let SvelteComponent; - - try { - SvelteComponent = require( `./generator/${dir}/main.html` ).default; - } catch ( err ) { - if ( !config.show ) console.log( addLineNumbers( code ) ); // eslint-disable-line no-console - throw err; - } - - return env() - .then( window => { - // Put the constructor on window for testing - window.SvelteComponent = SvelteComponent; - - const target = window.document.querySelector( 'main' ); - - const component = new SvelteComponent({ - target, - data: config.data - }); - - if ( config.html ) { - assert.htmlEqual( target.innerHTML, config.html ); - } - - if ( config.test ) { - config.test( assert, component, target, window ); - } else { - component.teardown(); - assert.equal( target.innerHTML, '' ); - } - }) - .catch( err => { - if ( !config.show ) console.log( addLineNumbers( code ) ); // eslint-disable-line no-console - throw err; - }); - }); - }); - }); - - describe( 'formats', () => { - function testAmd ( code, expectedId, dependencies, html ) { - const fn = new Function( 'define', code ); - - return env().then( window => { - function define ( id, deps, factory ) { - assert.equal( id, expectedId ); - assert.deepEqual( deps, Object.keys( dependencies ) ); - - const SvelteComponent = factory( ...Object.keys( dependencies ).map( key => dependencies[ key ] ) ); - - const main = window.document.body.querySelector( 'main' ); - const component = new SvelteComponent({ target: main }); - - assert.htmlEqual( main.innerHTML, html ); - - component.teardown(); - } - - define.amd = true; - - fn( define ); - }); - } - - function testCjs ( code, dependencyById, html ) { - const fn = new Function( 'module', 'exports', 'require', code ); - - return env().then( window => { - const module = { exports: {} }; - const require = id => { - return dependencyById[ id ]; - }; - - fn( module, module.exports, require ); - - const SvelteComponent = module.exports; - - const main = window.document.body.querySelector( 'main' ); - const component = new SvelteComponent({ target: main }); - - assert.htmlEqual( main.innerHTML, html ); - - component.teardown(); - }); - } - - function testIife ( code, name, globals, html ) { - const fn = new Function( Object.keys( globals ), `${code}\n\nreturn ${name};` ); - - return env().then( window => { - const SvelteComponent = fn( ...Object.keys( globals ).map( key => globals[ key ] ) ); - - const main = window.document.body.querySelector( 'main' ); - const component = new SvelteComponent({ target: main }); - - assert.htmlEqual( main.innerHTML, html ); - - component.teardown(); - }); - } - - describe( 'amd', () => { - it( 'generates an AMD module', () => { - const source = deindent` -
{{answer}}
- - - `; - - const { code } = svelte.compile( source, { - format: 'amd', - amd: { id: 'foo' } - }); - - return testAmd( code, 'foo', { answer: 42 }, `
42
` ); - }); - }); - - describe( 'cjs', () => { - it( 'generates a CommonJS module', () => { - const source = deindent` -
{{answer}}
- - - `; - - const { code } = svelte.compile( source, { - format: 'cjs' - }); - - return testCjs( code, { answer: 42 }, `
42
` ); - }); - }); - - describe( 'iife', () => { - it( 'generates a self-executing script', () => { - const source = deindent` -
{{answer}}
- - - `; - - const { code } = svelte.compile( source, { - format: 'iife', - name: 'Foo', - globals: { - answer: 'answer' - } - }); - - return testIife( code, 'Foo', { answer: 42 }, `
42
` ); - }); - }); - - describe( 'umd', () => { - it( 'generates a UMD build', () => { - const source = deindent` -
{{answer}}
- - - `; - - const { code } = svelte.compile( source, { - format: 'umd', - name: 'Foo', - globals: { - answer: 'answer' - }, - amd: { - id: 'foo' - } - }); - - return testAmd( code, 'foo', { answer: 42 }, `
42
` ) - .then( () => testCjs( code, { answer: 42 }, `
42
` ) ) - .then( () => testIife( code, 'Foo', { answer: 42 }, `
42
` ) ); - }); - }); - }); - - describe( 'sourcemaps', () => { - fs.readdirSync( 'test/sourcemaps' ).forEach( dir => { - if ( dir[0] === '.' ) return; - - const solo = exists( `test/sourcemaps/${dir}/solo` ); - - ( solo ? it.only : it )( dir, () => { - const input = fs.readFileSync( `test/sourcemaps/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' ); - const { code, map } = svelte.compile( input ); - - fs.writeFileSync( `test/sourcemaps/${dir}/output.js`, `${code}\n//# sourceMappingURL=output.js.map` ); - fs.writeFileSync( `test/sourcemaps/${dir}/output.js.map`, JSON.stringify( map, null, ' ' ) ); - - const { test } = require( `./sourcemaps/${dir}/test.js` ); - - const smc = new SourceMapConsumer( map ); - - const locateInSource = getLocator( input ); - const locateInGenerated = getLocator( code ); - - test({ assert, code, map, smc, locateInSource, locateInGenerated }); - }); - }); - }); - - describe( 'ssr', () => { - before( () => { - require( '../ssr/register' ); - }); - - fs.readdirSync( 'test/server-side-rendering' ).forEach( dir => { - if ( dir[0] === '.' ) return; - - const solo = exists( `test/server-side-rendering/${dir}/solo` ); - - ( solo ? it.only : it )( dir, () => { - const component = require( `./server-side-rendering/${dir}/main.html` ); - - const expectedHtml = tryToReadFile( `test/server-side-rendering/${dir}/_expected.html` ); - const expectedCss = tryToReadFile( `test/server-side-rendering/${dir}/_expected.css` ) || ''; - - const data = tryToLoadJson( `test/server-side-rendering/${dir}/data.json` ); - const html = component.render( data ); - const { css } = component.renderCss(); - - fs.writeFileSync( `test/server-side-rendering/${dir}/_actual.html`, html ); - if ( css ) fs.writeFileSync( `test/server-side-rendering/${dir}/_actual.css`, css ); - - assert.htmlEqual( html, expectedHtml ); - assert.equal( css.replace( /^\s+/gm, '' ), expectedCss.replace( /^\s+/gm, '' ) ); - }); - }); - }); -}); diff --git a/test/validate.js b/test/validate.js new file mode 100644 index 0000000000..597c9d7526 --- /dev/null +++ b/test/validate.js @@ -0,0 +1,60 @@ +import * as fs from 'fs'; +import assert from 'assert'; +import { svelte, exists, tryToLoadJson } from './helpers.js'; + +describe( 'validate', () => { + fs.readdirSync( 'test/validator' ).forEach( dir => { + if ( dir[0] === '.' ) return; + + const solo = exists( `test/validator/${dir}/solo` ); + + ( solo ? it.only : it )( dir, () => { + const input = fs.readFileSync( `test/validator/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' ); + + try { + const parsed = svelte.parse( input ); + + const errors = []; + const warnings = []; + + const { names } = svelte.validate( parsed, input, { + onerror ( error ) { + errors.push({ + message: error.message, + pos: error.pos, + loc: error.loc + }); + }, + + onwarn ( warning ) { + warnings.push({ + message: warning.message, + pos: warning.pos, + loc: warning.loc + }); + } + }); + + const expectedErrors = tryToLoadJson( `test/validator/${dir}/errors.json` ) || []; + const expectedWarnings = tryToLoadJson( `test/validator/${dir}/warnings.json` ) || []; + const expectedNames = tryToLoadJson( `test/validator/${dir}/names.json` ) || []; + + assert.deepEqual( errors, expectedErrors ); + assert.deepEqual( warnings, expectedWarnings ); + assert.deepEqual( names, expectedNames ); + } catch ( err ) { + if ( err.name !== 'ParseError' ) throw err; + + try { + const expected = require( `./validator/${dir}/error.json` ); + + assert.equal( err.shortMessage, expected.message ); + assert.deepEqual( err.loc, expected.loc ); + assert.equal( err.pos, expected.pos ); + } catch ( err2 ) { + throw err2.code === 'MODULE_NOT_FOUND' ? err : err2; + } + } + }); + }); +});