merge master -> gh-592

pull/603/head
Rich Harris 8 years ago
commit 3b70920991

@ -1,5 +1,18 @@
# Svelte changelog # Svelte changelog
## 1.21.0
* Always use `helpers` if referenced, not just for call expressions ([#575](https://github.com/sveltejs/svelte/issues/575))
* Fix parsing of `<textarea>` children ([#599](https://github.com/sveltejs/svelte/pull/599))
* Treat `<textarea>` value attributes and children as equivalent, and fail validation if both are present ([#599](https://github.com/sveltejs/svelte/pull/599))
* Fix `<textarea>` SSR ([#599](https://github.com/sveltejs/svelte/pull/599))
* Apply CSS transition styles immediately if transition has delay ([#574](https://github.com/sveltejs/svelte/issues/574))
* Ensure `transitionManager` is treeshakeable ([#593](https://github.com/sveltejs/svelte/issues/593))
* Fix for environments where `node.style.animation` is undefined ([#587](https://github.com/sveltejs/svelte/issues/587))
* Fix order of operations when dealing with `<select>` elements ([#590](https://github.com/sveltejs/svelte/issues/590))
* Downgrade 'invalid callee' to a warning ([#579](https://github.com/sveltejs/svelte/issues/579))
* Convert to TypeScript ([#573](https://github.com/sveltejs/svelte/pull/573))
## 1.20.2 ## 1.20.2
* Fix destruction of compound if-blocks with outros ([#572](https://github.com/sveltejs/svelte/pull/572)) * Fix destruction of compound if-blocks with outros ([#572](https://github.com/sveltejs/svelte/pull/572))

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "1.20.2", "version": "1.21.0",
"description": "The magical disappearing UI framework", "description": "The magical disappearing UI framework",
"main": "compiler/svelte.js", "main": "compiler/svelte.js",
"files": [ "files": [
@ -66,7 +66,7 @@
"glob": "^7.1.1", "glob": "^7.1.1",
"jsdom": "^9.9.1", "jsdom": "^9.9.1",
"locate-character": "^2.0.0", "locate-character": "^2.0.0",
"magic-string": "^0.19.0", "magic-string": "^0.21.1",
"mocha": "^3.2.0", "mocha": "^3.2.0",
"node-resolve": "^1.3.3", "node-resolve": "^1.3.3",
"nyc": "^10.0.0", "nyc": "^10.0.0",

@ -29,8 +29,11 @@ export default class Generator {
transitions: Set<string>; transitions: Set<string>;
importedComponents: Map<string, string>; importedComponents: Map<string, string>;
code: MagicString;
bindingGroups: string[]; bindingGroups: string[];
expectedProperties: Set<string>; expectedProperties: Set<string>;
cascade: boolean;
css: string; css: string;
cssId: string; cssId: string;
usesRefs: boolean; usesRefs: boolean;
@ -59,7 +62,8 @@ export default class Generator {
this.expectedProperties = new Set(); this.expectedProperties = new Set();
this.code = new MagicString( source ); this.code = new MagicString( source );
this.css = parsed.css ? processCss( parsed, this.code ) : null; this.cascade = options.cascade !== false; // TODO remove this option in v2
this.css = parsed.css ? processCss( parsed, this.code, this.cascade ) : null;
this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; this.cssId = parsed.css ? `svelte-${parsed.hash}` : '';
this.usesRefs = false; this.usesRefs = false;

@ -39,7 +39,7 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
prop prop
}); });
const setter = getSetter({ block, name, context: '_context', attribute, dependencies, value: 'value' }); const setter = getSetter({ block, name, snippet, context: '_context', attribute, dependencies, value: 'value' });
generator.hasComplexBindings = true; generator.hasComplexBindings = true;

@ -142,7 +142,7 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node
block.builders.mount.addBlock( deindent` block.builders.mount.addBlock( deindent`
var ${iteration} = ${head}; var ${iteration} = ${head};
while ( ${iteration} ) { while ( ${iteration} ) {
${iteration}.${mountOrIntro}( ${block.target}, null ); ${iteration}.${mountOrIntro}( ${block.target}, anchor );
${iteration} = ${iteration}.next; ${iteration} = ${iteration}.next;
} }
` ); ` );
@ -299,7 +299,7 @@ function unkeyed ( generator: DomGenerator, block: Block, state: State, node: No
if ( !state.parentNode ) { if ( !state.parentNode ) {
block.builders.mount.addBlock( deindent` block.builders.mount.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].${mountOrIntro}( ${block.target}, null ); ${iterations}[${i}].${mountOrIntro}( ${block.target}, anchor );
} }
` ); ` );
} }

@ -7,9 +7,16 @@ import Block from '../../Block';
import { Node } from '../../../../interfaces'; import { Node } from '../../../../interfaces';
import { State } from '../../interfaces'; import { State } from '../../interfaces';
function getObject ( node ) {
// TODO validation should ensure this is an Identifier or a MemberExpression
while ( node.type === 'MemberExpression' ) node = node.object;
return node;
}
export default function visitBinding ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node ) { export default function visitBinding ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node ) {
const { name, parts } = flattenReference( attribute.value ); const { name } = getObject( attribute.value );
const { snippet, contexts, dependencies } = block.contextualise( attribute.value ); const { snippet, contexts } = block.contextualise( attribute.value );
const dependencies = block.contextDependencies.has( name ) ? block.contextDependencies.get( name ) : [ name ];
if ( dependencies.length > 1 ) throw new Error( 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!' ); if ( dependencies.length > 1 ) throw new Error( 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!' );
@ -21,10 +28,10 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
const handler = block.getUniqueName( `${state.parentNode}_${eventName}_handler` ); const handler = block.getUniqueName( `${state.parentNode}_${eventName}_handler` );
const isMultipleSelect = node.name === 'select' && node.attributes.find( ( attr: Node ) => attr.name.toLowerCase() === 'multiple' ); // TODO use getStaticAttributeValue const isMultipleSelect = node.name === 'select' && node.attributes.find( ( attr: Node ) => attr.name.toLowerCase() === 'multiple' ); // TODO use getStaticAttributeValue
const type = getStaticAttributeValue( node, 'type' ); const type = getStaticAttributeValue( node, 'type' );
const bindingGroup = attribute.name === 'group' ? getBindingGroup( generator, parts.join( '.' ) ) : null; const bindingGroup = attribute.name === 'group' ? getBindingGroup( generator, attribute.value ) : null;
const value = getBindingValue( generator, block, state, node, attribute, isMultipleSelect, bindingGroup, type ); const value = getBindingValue( generator, block, state, node, attribute, isMultipleSelect, bindingGroup, type );
let setter = getSetter({ block, name, context: '_svelte', attribute, dependencies, value }); let setter = getSetter({ block, name, snippet, context: '_svelte', attribute, dependencies, value });
let updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`; let updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`;
const lock = block.alias( `${state.parentNode}_updating` ); const lock = block.alias( `${state.parentNode}_updating` );
let updateCondition = `!${lock}`; let updateCondition = `!${lock}`;
@ -183,14 +190,17 @@ function getBindingValue ( generator: DomGenerator, block: Block, state: State,
// <input type='range|number' bind:value> // <input type='range|number' bind:value>
if ( type === 'range' || type === 'number' ) { if ( type === 'range' || type === 'number' ) {
return `+${state.parentNode}.${attribute.name}`; return `${generator.helper( 'toNumber' )}( ${state.parentNode}.${attribute.name} )`;
} }
// everything else // everything else
return `${state.parentNode}.${attribute.name}`; return `${state.parentNode}.${attribute.name}`;
} }
function getBindingGroup ( generator: DomGenerator, keypath: string ) { function getBindingGroup ( generator: DomGenerator, value: Node ) {
const { parts } = flattenReference( value ); // TODO handle cases involving computed member expressions
const keypath = parts.join( '.' );
// TODO handle contextual bindings — `keypath` should include unique ID of // TODO handle contextual bindings — `keypath` should include unique ID of
// each block that provides context // each block that provides context
let index = generator.bindingGroups.indexOf( keypath ); let index = generator.bindingGroups.indexOf( keypath );

@ -46,7 +46,7 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
block.mount( name, state.parentNode ); block.mount( name, state.parentNode );
// add CSS encapsulation attribute // add CSS encapsulation attribute
if ( generator.cssId && state.isTopLevel ) { if ( generator.cssId && ( !generator.cascade || state.isTopLevel ) ) {
block.builders.create.addLine( `${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );` ); block.builders.create.addLine( `${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );` );
} }

@ -1,14 +1,16 @@
import deindent from '../../../../../utils/deindent.js'; import deindent from '../../../../../utils/deindent.js';
export default function getSetter ({ block, name, context, attribute, dependencies, value }) { export default function getSetter ({ block, name, snippet, context, attribute, dependencies, value }) {
const tail = attribute.value.type === 'MemberExpression' ? getTailSnippet( attribute.value ) : ''; const tail = attribute.value.type === 'MemberExpression' ? getTailSnippet( attribute.value ) : '';
if ( block.contexts.has( name ) ) { if ( block.contexts.has( name ) ) {
const prop = dependencies[0]; const prop = dependencies[0];
const computed = isComputed( attribute.value );
return deindent` return deindent`
var list = this.${context}.${block.listNames.get( name )}; var list = this.${context}.${block.listNames.get( name )};
var index = this.${context}.${block.indexNames.get( name )}; var index = this.${context}.${block.indexNames.get( name )};
${computed && `var state = ${block.component}.get();`}
list[index]${tail} = ${value}; list[index]${tail} = ${value};
${block.component}._set({ ${prop}: ${block.component}.get( '${prop}' ) }); ${block.component}._set({ ${prop}: ${block.component}.get( '${prop}' ) });
@ -19,9 +21,9 @@ export default function getSetter ({ block, name, context, attribute, dependenci
const alias = block.alias( name ); const alias = block.alias( name );
return deindent` return deindent`
var ${alias} = ${block.component}.get( '${name}' ); var state = ${block.component}.get();
${alias}${tail} = ${value}; ${snippet} = ${value};
${block.component}._set({ ${name}: ${alias} }); ${block.component}._set({ ${name}: state.${name} });
`; `;
} }
@ -35,3 +37,12 @@ function getTailSnippet ( node ) {
return `[✂${start}-${end}✂]`; return `[✂${start}-${end}✂]`;
} }
function isComputed ( node ) {
while ( node.type === 'MemberExpression' ) {
if ( node.computed ) return true;
node = node.object;
}
return false;
}

@ -13,6 +13,7 @@ export class SsrGenerator extends Generator {
super( parsed, source, name, options ); super( parsed, source, name, options );
this.bindings = []; this.bindings = [];
this.renderCode = ''; this.renderCode = '';
this.elementDepth = 0;
} }
append ( code: string ) { append ( code: string ) {

@ -50,7 +50,7 @@ export default function visitElement ( generator: SsrGenerator, block: Block, no
} }
}); });
if ( generator.cssId && !generator.elementDepth ) { if ( generator.cssId && ( !generator.cascade || generator.elementDepth === 0 ) ) {
openingTag += ` ${generator.cssId}`; openingTag += ` ${generator.cssId}`;
} }

@ -1,8 +1,9 @@
import MagicString from 'magic-string';
import { Parsed, Node } from '../../interfaces'; import { Parsed, Node } from '../../interfaces';
const commentsPattern = /\/\*[\s\S]*?\*\//g; const commentsPattern = /\/\*[\s\S]*?\*\//g;
export default function processCss ( parsed: Parsed, code ) { export default function processCss ( parsed: Parsed, code: MagicString, cascade: boolean ) {
const css = parsed.css.content.styles; const css = parsed.css.content.styles;
const offset = parsed.css.content.start; const offset = parsed.css.content.start;
@ -14,9 +15,13 @@ export default function processCss ( parsed: Parsed, code ) {
if ( node.type === 'Atrule' && node.name.toLowerCase() === 'keyframes' ) { if ( node.type === 'Atrule' && node.name.toLowerCase() === 'keyframes' ) {
node.expression.children.forEach( ( expression: Node ) => { node.expression.children.forEach( ( expression: Node ) => {
if ( expression.type === 'Identifier' ) { if ( expression.type === 'Identifier' ) {
const newName = `svelte-${parsed.hash}-${expression.name}`; if ( expression.name.startsWith( '-global-' ) ) {
code.overwrite( expression.start, expression.end, newName ); code.remove( expression.start, expression.start + 8 );
keyframes.set( expression.name, newName ); } else {
const newName = `svelte-${parsed.hash}-${expression.name}`;
code.overwrite( expression.start, expression.end, newName );
keyframes.set( expression.name, newName );
}
} }
}); });
} else if ( node.children ) { } else if ( node.children ) {
@ -30,26 +35,63 @@ export default function processCss ( parsed: Parsed, code ) {
function transform ( rule: Node ) { function transform ( rule: Node ) {
rule.selector.children.forEach( ( selector: Node ) => { rule.selector.children.forEach( ( selector: Node ) => {
const start = selector.start - offset; if ( cascade ) {
const end = selector.end - offset; // TODO disable cascading (without :global(...)) in v2
const start = selector.start - offset;
const end = selector.end - offset;
const selectorString = css.slice( start, end ); const selectorString = css.slice( start, end );
const firstToken = selector.children[0]; const firstToken = selector.children[0];
let transformed; let transformed;
if ( firstToken.type === 'TypeSelector' ) { if ( firstToken.type === 'TypeSelector' ) {
const insert = firstToken.end - offset; const insert = firstToken.end - offset;
const head = css.slice( start, insert ); const head = css.slice( start, insert );
const tail = css.slice( insert, end ); const tail = css.slice( insert, end );
transformed = `${head}${attr}${tail}, ${attr} ${selectorString}`; transformed = `${head}${attr}${tail}, ${attr} ${selectorString}`;
} else { } else {
transformed = `${attr}${selectorString}, ${attr} ${selectorString}`; transformed = `${attr}${selectorString}, ${attr} ${selectorString}`;
}
code.overwrite( selector.start, selector.end, transformed );
} }
code.overwrite( start + offset, end + offset, transformed ); else {
let shouldTransform = true;
let c = selector.start;
selector.children.forEach( ( child: Node ) => {
if ( child.type === 'WhiteSpace' || child.type === 'Combinator' ) {
code.appendLeft( c, attr );
shouldTransform = true;
return;
}
if ( !shouldTransform ) return;
if ( child.type === 'PseudoClassSelector' ) {
// `:global(xyz)` > xyz
if ( child.name === 'global' ) {
const first = child.children[0];
const last = child.children[child.children.length - 1];
code.remove( child.start, first.start ).remove( last.end, child.end );
} else {
code.prependRight( c, attr );
}
shouldTransform = false;
}
c = child.end;
});
if ( shouldTransform ) {
code.appendLeft( c, attr );
}
}
}); });
rule.block.children.forEach( ( block: Node ) => { rule.block.children.forEach( ( block: Node ) => {

@ -40,6 +40,7 @@ export interface CompileOptions {
dev?: boolean; dev?: boolean;
shared?: boolean | string; shared?: boolean | string;
cascade?: boolean;
onerror?: (error: Error) => void onerror?: (error: Error) => void
onwarn?: (warning: Warning) => void onwarn?: (warning: Warning) => void

@ -104,7 +104,7 @@ export function readBindingDirective ( parser: Parser, start: number, name: stri
value = parseExpressionAt( source, a ); value = parseExpressionAt( source, a );
if ( value.type !== 'Identifier' && value.type !== 'MemberExpression' ) { if ( value.type !== 'Identifier' && value.type !== 'MemberExpression' ) {
parser.error( `Expected valid property name` ); parser.error( `Cannot bind to rvalue`, value.start );
} }
parser.allowWhitespace(); parser.allowWhitespace();

@ -61,4 +61,8 @@ export function getBindingGroupValue ( group ) {
if ( group[i].checked ) value.push( group[i].__value ); if ( group[i].checked ) value.push( group[i].__value );
} }
return value; return value;
}
export function toNumber ( value ) {
return value === '' ? undefined : +value;
} }

@ -1,10 +1,11 @@
export function noop () {} export function noop () {}
export function assign ( target ) { export function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) { var k, source, i = 1, len = arguments.length;
var source = arguments[i]; for ( ; i < len; i++ ) {
for ( var k in source ) target[k] = source[k]; source = arguments[i];
for ( k in source ) target[k] = source[k];
} }
return target; return target;
} }

@ -1,6 +1,15 @@
import assert from 'assert'; import assert from 'assert';
import * as fs from 'fs'; import * as fs from 'fs';
import { svelte, exists } from '../helpers.js'; import { svelte } from '../helpers.js';
function tryRequire ( file ) {
try {
return require( file ).default;
} catch ( err ) {
if ( err.code !== 'MODULE_NOT_FOUND' ) throw err;
return null;
}
}
describe( 'css', () => { describe( 'css', () => {
fs.readdirSync( 'test/css/samples' ).forEach( dir => { fs.readdirSync( 'test/css/samples' ).forEach( dir => {
@ -14,9 +23,10 @@ describe( 'css', () => {
} }
( solo ? it.only : it )( dir, () => { ( solo ? it.only : it )( dir, () => {
const config = tryRequire( `./samples/${dir}/_config.js` ) || {};
const input = fs.readFileSync( `test/css/samples/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' ); const input = fs.readFileSync( `test/css/samples/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' );
const actual = svelte.compile( input ).css; const actual = svelte.compile( input, config ).css;
fs.writeFileSync( `test/css/samples/${dir}/_actual.css`, actual ); fs.writeFileSync( `test/css/samples/${dir}/_actual.css`, actual );
const expected = fs.readFileSync( `test/css/samples/${dir}/expected.css`, 'utf-8' ); const expected = fs.readFileSync( `test/css/samples/${dir}/expected.css`, 'utf-8' );

@ -0,0 +1,13 @@
@keyframes why {
0% { color: red; }
100% { color: blue; }
}
[svelte-2486527405].animated, [svelte-2486527405] .animated {
animation: why 2s;
}
[svelte-2486527405].also-animated, [svelte-2486527405] .also-animated {
animation: not-defined-here 2s;
}

@ -0,0 +1,17 @@
<div class='animated'>animated</div>
<div class='also-animated'>also animated</div>
<style>
@keyframes -global-why {
0% { color: red; }
100% { color: blue; }
}
.animated {
animation: why 2s;
}
.also-animated {
animation: not-defined-here 2s;
}
</style>

@ -0,0 +1,3 @@
export default {
cascade: false
};

@ -0,0 +1,12 @@
div {
color: red;
}
div.foo {
color: blue;
}
.foo {
font-weight: bold;
}

@ -0,0 +1,16 @@
<div>red</div>
<div class='foo'>bold/blue</div>
<style>
:global(div) {
color: red;
}
:global(div.foo) {
color: blue;
}
:global(.foo) {
font-weight: bold;
}
</style>

@ -0,0 +1,13 @@
@keyframes svelte-776829126-why {
0% { color: red; }
100% { color: blue; }
}
[svelte-776829126].animated, [svelte-776829126] .animated {
animation: svelte-776829126-why 2s;
}
[svelte-776829126].also-animated, [svelte-776829126] .also-animated {
animation: not-defined-here 2s;
}

@ -0,0 +1,17 @@
<div class='animated'>animated</div>
<div class='also-animated'>also animated</div>
<style>
@keyframes why {
0% { color: red; }
100% { color: blue; }
}
.animated {
animation: why 2s;
}
.also-animated {
animation: not-defined-here 2s;
}
</style>

@ -0,0 +1,3 @@
export default {
cascade: false
};

@ -0,0 +1,12 @@
div[svelte-4161687011] {
color: red;
}
div.foo[svelte-4161687011] {
color: blue;
}
.foo[svelte-4161687011] {
font-weight: bold;
}

@ -0,0 +1,16 @@
<div>red</div>
<div class='foo'>bold/blue</div>
<style>
div {
color: red;
}
div.foo {
color: blue;
}
.foo {
font-weight: bold;
}
</style>

@ -1,9 +1,10 @@
function noop () {} function noop () {}
function assign ( target ) { function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) { var k, source, i = 1, len = arguments.length;
var source = arguments[i]; for ( ; i < len; i++ ) {
for ( var k in source ) target[k] = source[k]; source = arguments[i];
for ( k in source ) target[k] = source[k];
} }
return target; return target;

@ -1,9 +1,10 @@
function noop () {} function noop () {}
function assign ( target ) { function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) { var k, source, i = 1, len = arguments.length;
var source = arguments[i]; for ( ; i < len; i++ ) {
for ( var k in source ) target[k] = source[k]; source = arguments[i];
for ( k in source ) target[k] = source[k];
} }
return target; return target;

@ -1,9 +1,10 @@
function noop () {} function noop () {}
function assign ( target ) { function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) { var k, source, i = 1, len = arguments.length;
var source = arguments[i]; for ( ; i < len; i++ ) {
for ( var k in source ) target[k] = source[k]; source = arguments[i];
for ( k in source ) target[k] = source[k];
} }
return target; return target;
@ -156,7 +157,7 @@ function create_main_fragment ( state, component ) {
return { return {
mount: function ( target, anchor ) { mount: function ( target, anchor ) {
for ( var i = 0; i < each_block_iterations.length; i += 1 ) { for ( var i = 0; i < each_block_iterations.length; i += 1 ) {
each_block_iterations[i].mount( target, null ); each_block_iterations[i].mount( target, anchor );
} }
insertNode( text, target, anchor ); insertNode( text, target, anchor );

@ -19,7 +19,7 @@ function create_main_fragment ( state, component ) {
return { return {
mount: function ( target, anchor ) { mount: function ( target, anchor ) {
for ( var i = 0; i < each_block_iterations.length; i += 1 ) { for ( var i = 0; i < each_block_iterations.length; i += 1 ) {
each_block_iterations[i].mount( target, null ); each_block_iterations[i].mount( target, anchor );
} }
insertNode( text, target, anchor ); insertNode( text, target, anchor );
@ -168,4 +168,4 @@ SvelteComponent.prototype.teardown = SvelteComponent.prototype.destroy = functio
this._torndown = true; this._torndown = true;
}; };
export default SvelteComponent; export default SvelteComponent;

@ -1,7 +1,8 @@
function assign ( target ) { function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) { var k, source, i = 1, len = arguments.length;
var source = arguments[i]; for ( ; i < len; i++ ) {
for ( var k in source ) target[k] = source[k]; source = arguments[i];
for ( k in source ) target[k] = source[k];
} }
return target; return target;

@ -1,9 +1,10 @@
function noop () {} function noop () {}
function assign ( target ) { function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) { var k, source, i = 1, len = arguments.length;
var source = arguments[i]; for ( ; i < len; i++ ) {
for ( var k in source ) target[k] = source[k]; source = arguments[i];
for ( k in source ) target[k] = source[k];
} }
return target; return target;

@ -1,9 +1,10 @@
function noop () {} function noop () {}
function assign ( target ) { function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) { var k, source, i = 1, len = arguments.length;
var source = arguments[i]; for ( ; i < len; i++ ) {
for ( var k in source ) target[k] = source[k]; source = arguments[i];
for ( k in source ) target[k] = source[k];
} }
return target; return target;

@ -1,9 +1,10 @@
import Imported from 'Imported.html'; import Imported from 'Imported.html';
function assign ( target ) { function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) { var k, source, i = 1, len = arguments.length;
var source = arguments[i]; for ( ; i < len; i++ ) {
for ( var k in source ) target[k] = source[k]; source = arguments[i];
for ( k in source ) target[k] = source[k];
} }
return target; return target;

@ -1,9 +1,10 @@
function noop () {} function noop () {}
function assign ( target ) { function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) { var k, source, i = 1, len = arguments.length;
var source = arguments[i]; for ( ; i < len; i++ ) {
for ( var k in source ) target[k] = source[k]; source = arguments[i];
for ( k in source ) target[k] = source[k];
} }
return target; return target;

@ -1,9 +1,10 @@
function noop () {} function noop () {}
function assign ( target ) { function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) { var k, source, i = 1, len = arguments.length;
var source = arguments[i]; for ( ; i < len; i++ ) {
for ( var k in source ) target[k] = source[k]; source = arguments[i];
for ( k in source ) target[k] = source[k];
} }
return target; return target;

@ -0,0 +1,8 @@
{
"message": "Cannot bind to rvalue",
"pos": 19,
"loc": {
"line": 1,
"column": 19
}
}

@ -29,5 +29,15 @@ export default {
<input type='number'> <input type='number'>
<p>number 44</p> <p>number 44</p>
` ); ` );
// empty string should be treated as undefined
input.value = '';
input.dispatchEvent( event );
assert.equal( component.get( 'count' ), undefined );
assert.htmlEqual( target.innerHTML, `
<input type='number'>
<p>undefined undefined</p>
` );
} }
}; };

@ -0,0 +1,55 @@
export default {
data: {
prop: 'bar',
obj: {
foo: 'a',
bar: 'b',
baz: 'c'
}
},
html: `
<input>
<pre>{"foo":"a","bar":"b","baz":"c"}</pre>
`,
test ( assert, component, target, window ) {
const input = target.querySelector( 'input' );
const event = new window.Event( 'input' );
assert.equal( input.value, 'b' );
// edit bar
input.value = 'e';
input.dispatchEvent( event );
assert.htmlEqual( target.innerHTML, `
<input>
<pre>{"foo":"a","bar":"e","baz":"c"}</pre>
` );
// edit baz
component.set({ prop: 'baz' });
assert.equal( input.value, 'c' );
input.value = 'f';
input.dispatchEvent( event );
assert.htmlEqual( target.innerHTML, `
<input>
<pre>{"foo":"a","bar":"e","baz":"f"}</pre>
` );
// edit foo
component.set({ prop: 'foo' });
assert.equal( input.value, 'a' );
input.value = 'd';
input.dispatchEvent( event );
assert.htmlEqual( target.innerHTML, `
<input>
<pre>{"foo":"d","bar":"e","baz":"f"}</pre>
` );
}
};

@ -0,0 +1,2 @@
<input bind:value='obj[prop]'>
<pre>{{JSON.stringify(obj)}}</pre>

@ -0,0 +1,30 @@
export default {
data: {
prop: 'name',
user: {
name: 'alice'
}
},
html: `<input>\n<p>hello alice</p>`,
test ( assert, component, target, window ) {
const input = target.querySelector( 'input' );
assert.equal( input.value, 'alice' );
const event = new window.Event( 'input' );
input.value = 'bob';
input.dispatchEvent( event );
assert.equal( target.innerHTML, `<input>\n<p>hello bob</p>` );
const user = component.get( 'user' );
user.name = 'carol';
component.set({ user });
assert.equal( input.value, 'carol' );
assert.equal( target.innerHTML, `<input>\n<p>hello carol</p>` );
}
};

@ -0,0 +1,2 @@
<input bind:value='user[prop]'>
<p>hello {{user.name}}</p>

@ -0,0 +1,55 @@
export default {
data: {
prop: 'bar',
objects: [{
foo: 'a',
bar: 'b',
baz: 'c'
}]
},
html: `
<input>
<pre>{"foo":"a","bar":"b","baz":"c"}</pre>
`,
test ( assert, component, target, window ) {
const input = target.querySelector( 'input' );
const event = new window.Event( 'input' );
assert.equal( input.value, 'b' );
// edit bar
input.value = 'e';
input.dispatchEvent( event );
assert.htmlEqual( target.innerHTML, `
<input>
<pre>{"foo":"a","bar":"e","baz":"c"}</pre>
` );
// edit baz
component.set({ prop: 'baz' });
assert.equal( input.value, 'c' );
input.value = 'f';
input.dispatchEvent( event );
assert.htmlEqual( target.innerHTML, `
<input>
<pre>{"foo":"a","bar":"e","baz":"f"}</pre>
` );
// edit foo
component.set({ prop: 'foo' });
assert.equal( input.value, 'a' );
input.value = 'd';
input.dispatchEvent( event );
assert.htmlEqual( target.innerHTML, `
<input>
<pre>{"foo":"d","bar":"e","baz":"f"}</pre>
` );
}
};

@ -0,0 +1,4 @@
{{#each objects as obj}}
<input bind:value='obj[prop]'>
<pre>{{JSON.stringify(obj)}}</pre>
{{/each}}

@ -0,0 +1,5 @@
{{#if show}}
{{#each fields as field}}
<span>{{field}}</span>
{{/each}}
{{/if}}

@ -0,0 +1,36 @@
export default {
data: {
show: false,
fields: [1, 2]
},
html: `<div></div>`,
test ( assert, component, target ) {
component.set({
show: true,
fields: [1, 2, 3]
});
assert.htmlEqual( target.innerHTML, `
<div>
<span>1</span>
<span>2</span>
<span>3</span>
</div>
` );
component.set({
fields: [1, 2, 3, 4]
});
assert.htmlEqual( target.innerHTML, `
<div>
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
</div>
` );
}
};

@ -0,0 +1,13 @@
<div>
<Nested :show :fields/>
</div>
<script>
import Nested from './Nested.html';
export default {
components: {
Nested
}
};
</script>
Loading…
Cancel
Save