get a few binding tests passing

pull/1864/head
Rich Harris 7 years ago
parent b5a42e6cf7
commit 780c6803a5

@ -74,8 +74,13 @@ export default class BindingWrapper {
let updateConditions: string[] = [];
const { name } = getObject(this.node.expression.node);
const { snippet } = this.node.expression;
// TODO unfortunate code is necessary because we need to use `ctx`
// inside the fragment, but not inside the <script>
const contextless_snippet = this.parent.renderer.component.source.slice(this.node.expression.node.start, this.node.expression.node.end);
// special case: if you have e.g. `<input type=checkbox bind:checked=selected.done>`
// and `selected` is an object chosen with a <select>, then when `checked` changes,
// we need to tell the component to update all the values `selected` might be
@ -94,7 +99,7 @@ export default class BindingWrapper {
// view to model
const valueFromDom = getValueFromDom(renderer, this.parent, this);
const handler = getEventHandler(this, renderer, block, name, snippet, dependencies, valueFromDom);
const handler = getEventHandler(this, renderer, block, name, contextless_snippet, dependencies, valueFromDom);
// model to view
let updateDom = getDomUpdater(parent, this, snippet);
@ -154,7 +159,8 @@ export default class BindingWrapper {
initialUpdate: initialUpdate,
needsLock: !isReadOnly && needsLock,
updateCondition: updateConditions.length ? updateConditions.join(' && ') : undefined,
isReadOnlyMediaAttribute: this.isReadOnlyMediaAttribute()
isReadOnlyMediaAttribute: this.isReadOnlyMediaAttribute(),
dependencies: this.node.expression.dependencies
};
}
}
@ -213,7 +219,6 @@ function getEventHandler(
dependencies: Set<string>,
value: string
) {
const storeDependencies = [...dependencies].filter(prop => prop[0] === '$').map(prop => prop.slice(1));
let dependenciesArray = [...dependencies].filter(prop => prop[0] !== '$');
if (binding.node.isContextual) {
@ -226,51 +231,27 @@ function getEventHandler(
return {
usesContext: true,
usesState: true,
usesStore: storeDependencies.length > 0,
mutation: `${head()}${tail} = ${value};`,
props: dependenciesArray.map(prop => `${prop}: ctx.${prop}`),
storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`)
props: dependenciesArray.map(prop => `${prop}: ctx.${prop}`)
};
}
if (binding.node.expression.node.type === 'MemberExpression') {
// This is a little confusing, and should probably be tidied up
// at some point. It addresses a tricky bug (#893), wherein
// Svelte tries to `set()` a computed property, which throws an
// error in dev mode. a) it's possible that we should be
// replacing computations with *their* dependencies, and b)
// we should probably populate `component.target.readonly` sooner so
// that we don't have to do the `.some()` here
dependenciesArray = dependenciesArray.filter(prop => !renderer.component.computations.some(computation => computation.key === prop));
return {
usesContext: false,
usesState: true,
usesStore: storeDependencies.length > 0,
mutation: `${snippet} = ${value}`,
props: dependenciesArray.map((prop: string) => `${prop}: ctx.${prop}`),
storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`)
mutation: `${snippet} = ${value};`,
props: dependenciesArray.map((prop: string) => `${prop}: ctx.${prop}`)
};
}
let props;
let storeProps;
if (name[0] === '$') {
props = [];
storeProps = [`${name.slice(1)}: ${value}`];
} else {
props = [`${name}: ${value}`];
storeProps = [];
}
const props = [`${name}: ${value}`];
return {
usesContext: false,
usesState: false,
usesStore: false,
mutation: null,
props,
storeProps
mutation: `${snippet} = ${value};`,
props
};
}
@ -311,5 +292,5 @@ function getValueFromDom(
}
// everything else
return `${element.var}.${name}`;
return `this.${name}`;
}

@ -16,6 +16,7 @@ import StyleAttributeWrapper from './StyleAttribute';
import { dimensions } from '../../../../utils/patterns';
import Binding from './Binding';
import InlineComponentWrapper from '../InlineComponent';
import addToSet from '../../../../utils/addToSet';
const events = [
{
@ -438,7 +439,11 @@ export default class ElementWrapper extends Wrapper {
const needsLock = group.bindings.some(binding => binding.needsLock);
const deps = new Set();
group.bindings.forEach(binding => {
addToSet(deps, binding.dependencies);
if (!binding.updateDom) return;
const updateConditions = needsLock ? [`!${lock}`] : [];
@ -449,21 +454,8 @@ export default class ElementWrapper extends Wrapper {
);
});
const usesStore = group.bindings.some(binding => binding.handler.usesStore);
const mutations = group.bindings.map(binding => binding.handler.mutation).filter(Boolean).join('\n');
const props = new Set();
const storeProps = new Set();
group.bindings.forEach(binding => {
binding.handler.props.forEach(prop => {
props.add(prop);
});
binding.handler.storeProps.forEach(prop => {
storeProps.add(prop);
});
}); // TODO use stringifyProps here, once indenting is fixed
// media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our
// own hands
@ -473,21 +465,23 @@ export default class ElementWrapper extends Wrapper {
block.addVariable(animation_frame);
}
block.builders.init.addBlock(deindent`
function ${handler}() {
${
animation_frame && deindent`
cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});`
// TODO figure out how to handle locks
this.renderer.component.event_handlers.push({
name: handler,
body: deindent`
function ${handler}() {
${
animation_frame && deindent`
cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});`
}
${mutations.length > 0 && mutations}
${Array.from(deps).map(dep => `$$make_dirty('${dep}');`)}
console.log('changed');
}
${usesStore && `var $ = #component.store.get();`}
${needsLock && `${lock} = true;`}
${mutations.length > 0 && mutations}
${props.size > 0 && `#component.set({ ${Array.from(props).join(', ')} });`}
${storeProps.size > 0 && `#component.store.set({ ${Array.from(storeProps).join(', ')} });`}
${needsLock && `${lock} = false;`}
}
`);
`
});
group.events.forEach(name => {
if (name === 'resize') {
@ -496,7 +490,7 @@ export default class ElementWrapper extends Wrapper {
block.addVariable(resize_listener);
block.builders.mount.addLine(
`${resize_listener} = @addResizeListener(${this.var}, ${handler});`
`${resize_listener} = @addResizeListener(${this.var}, ctx.${handler});`
);
block.builders.destroy.addLine(
@ -504,11 +498,11 @@ export default class ElementWrapper extends Wrapper {
);
} else {
block.builders.hydrate.addLine(
`@addListener(${this.var}, "${name}", ${handler});`
`@addListener(${this.var}, "${name}", ctx.${handler});`
);
block.builders.destroy.addLine(
`@removeListener(${this.var}, "${name}", ${handler});`
`@removeListener(${this.var}, "${name}", ctx.${handler});`
);
}
});

@ -121,7 +121,7 @@ export default function(node, renderer, options) {
}
node.bindings.forEach(binding => {
const { name, value: { snippet } } = binding;
const { name, expression: { snippet } } = binding;
if (name === 'group') {
// TODO server-render group bindings

@ -33,32 +33,32 @@ export default {
<p>1 completed</p>
`,
test ( assert, component, target, window ) {
const inputs = [ ...target.querySelectorAll( 'input' ) ];
async test(assert, component, target, window) {
const inputs = [ ...target.querySelectorAll('input') ];
assert.ok( inputs[0].checked );
assert.ok( !inputs[1].checked );
assert.ok( !inputs[2].checked );
assert.ok(inputs[0].checked);
assert.ok(!inputs[1].checked);
assert.ok(!inputs[2].checked);
const event = new window.Event( 'change' );
const event = new window.Event('change');
inputs[1].checked = true;
inputs[1].dispatchEvent( event );
await inputs[1].dispatchEvent(event);
assert.equal( component.numCompleted, 2 );
assert.htmlEqual( target.innerHTML, `
assert.equal(component.numCompleted, 2);
assert.htmlEqual(target.innerHTML, `
<div><input type="checkbox"><p>one</p></div><div><input type="checkbox"><p>two</p></div><div><input type="checkbox"><p>three</p></div>
<p>2 completed</p>
` );
`);
const items = component.items;
items[2].completed = true;
component.items = items;
assert.ok( inputs[2].checked );
assert.htmlEqual( target.innerHTML, `
assert.ok(inputs[2].checked);
assert.htmlEqual(target.innerHTML, `
<div><input type="checkbox"><p>one</p></div><div><input type="checkbox"><p>two</p></div><div><input type="checkbox"><p>three</p></div>
<p>3 completed</p>
` );
`);
}
};

@ -13,19 +13,19 @@ export default {
<p>true</p>
`,
test ( assert, component, target, window ) {
const input = target.querySelector( 'input' );
assert.equal( input.checked, true );
async test(assert, component, target, window) {
const input = target.querySelector('input');
assert.equal(input.checked, true);
const event = new window.Event( 'change' );
const event = new window.Event('change');
input.checked = false;
input.dispatchEvent( event );
await input.dispatchEvent(event);
assert.equal( target.innerHTML, `<input type="checkbox">\n<p>false</p>` );
assert.equal(target.innerHTML, `<input type="checkbox">\n<p>false</p>`);
component.foo = true;
assert.equal( input.checked, true );
assert.equal( target.innerHTML, `<input type="checkbox">\n<p>true</p>` );
assert.equal(input.checked, true);
assert.equal(target.innerHTML, `<input type="checkbox">\n<p>true</p>`);
}
};

@ -13,14 +13,14 @@ export default {
<p>number 42</p>
`,
test(assert, component, target, window) {
async test(assert, component, target, window) {
const input = target.querySelector('input');
assert.equal(input.value, '42');
const event = new window.Event('change');
input.value = '43';
input.dispatchEvent(event);
await input.dispatchEvent(event);
assert.equal(component.count, 43);
assert.htmlEqual(target.innerHTML, `

@ -13,14 +13,14 @@ export default {
<p>number 42</p>
`,
test(assert, component, target, window) {
async test(assert, component, target, window) {
const input = target.querySelector('input');
assert.equal(input.value, '42');
const event = new window.Event('input');
input.value = '43';
input.dispatchEvent(event);
await input.dispatchEvent(event);
assert.equal(component.count, 43);
assert.htmlEqual(target.innerHTML, `

@ -27,7 +27,7 @@ export default {
</div>
`,
test(assert, component, target, window) {
async test(assert, component, target, window) {
const inputs = [...target.querySelectorAll('input')];
const items = component.items;
const event = new window.Event('input');

@ -15,7 +15,7 @@ export default {
<p>hello alice</p>
`,
test(assert, component, target, window) {
async test(assert, component, target, window) {
const input = target.querySelector('input');
assert.equal(input.value, 'alice');
@ -23,7 +23,7 @@ export default {
const event = new window.Event('input');
input.value = 'bob';
input.dispatchEvent(event);
await input.dispatchEvent(event);
assert.htmlEqual(target.innerHTML, `
<input>

@ -13,14 +13,14 @@ export default {
<p>hello world</p>
`,
test(assert, component, target, window) {
async test(assert, component, target, window) {
const input = target.querySelector('input');
assert.equal(input.value, 'world');
const event = new window.Event('input');
input.value = 'everybody';
input.dispatchEvent(event);
await input.dispatchEvent(event);
assert.htmlEqual(target.innerHTML, `
<input>

@ -13,14 +13,14 @@ export default {
<p>some text</p>
`,
test(assert, component, target, window) {
async test(assert, component, target, window) {
const textarea = target.querySelector('textarea');
assert.equal(textarea.value, 'some text');
const event = new window.Event('input');
textarea.value = 'hello';
textarea.dispatchEvent(event);
await textarea.dispatchEvent(event);
assert.htmlEqual(target.innerHTML, `
<textarea></textarea>

Loading…
Cancel
Save