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[] = []; let updateConditions: string[] = [];
const { name } = getObject(this.node.expression.node); const { name } = getObject(this.node.expression.node);
const { snippet } = this.node.expression; 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>` // 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, // 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 // 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 // view to model
const valueFromDom = getValueFromDom(renderer, this.parent, this); 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 // model to view
let updateDom = getDomUpdater(parent, this, snippet); let updateDom = getDomUpdater(parent, this, snippet);
@ -154,7 +159,8 @@ export default class BindingWrapper {
initialUpdate: initialUpdate, initialUpdate: initialUpdate,
needsLock: !isReadOnly && needsLock, needsLock: !isReadOnly && needsLock,
updateCondition: updateConditions.length ? updateConditions.join(' && ') : undefined, 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>, dependencies: Set<string>,
value: string value: string
) { ) {
const storeDependencies = [...dependencies].filter(prop => prop[0] === '$').map(prop => prop.slice(1));
let dependenciesArray = [...dependencies].filter(prop => prop[0] !== '$'); let dependenciesArray = [...dependencies].filter(prop => prop[0] !== '$');
if (binding.node.isContextual) { if (binding.node.isContextual) {
@ -226,51 +231,27 @@ function getEventHandler(
return { return {
usesContext: true, usesContext: true,
usesState: true, usesState: true,
usesStore: storeDependencies.length > 0,
mutation: `${head()}${tail} = ${value};`, mutation: `${head()}${tail} = ${value};`,
props: dependenciesArray.map(prop => `${prop}: ctx.${prop}`), props: dependenciesArray.map(prop => `${prop}: ctx.${prop}`)
storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`)
}; };
} }
if (binding.node.expression.node.type === 'MemberExpression') { 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 { return {
usesContext: false, usesContext: false,
usesState: true, usesState: true,
usesStore: storeDependencies.length > 0, mutation: `${snippet} = ${value};`,
mutation: `${snippet} = ${value}`, props: dependenciesArray.map((prop: string) => `${prop}: ctx.${prop}`)
props: dependenciesArray.map((prop: string) => `${prop}: ctx.${prop}`),
storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`)
}; };
} }
let props; const props = [`${name}: ${value}`];
let storeProps;
if (name[0] === '$') {
props = [];
storeProps = [`${name.slice(1)}: ${value}`];
} else {
props = [`${name}: ${value}`];
storeProps = [];
}
return { return {
usesContext: false, usesContext: false,
usesState: false, usesState: false,
usesStore: false, mutation: `${snippet} = ${value};`,
mutation: null, props
props,
storeProps
}; };
} }
@ -311,5 +292,5 @@ function getValueFromDom(
} }
// everything else // everything else
return `${element.var}.${name}`; return `this.${name}`;
} }

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

@ -33,7 +33,7 @@ export default {
<p>1 completed</p> <p>1 completed</p>
`, `,
test ( assert, component, target, window ) { async test(assert, component, target, window) {
const inputs = [ ...target.querySelectorAll('input') ]; const inputs = [ ...target.querySelectorAll('input') ];
assert.ok(inputs[0].checked); assert.ok(inputs[0].checked);
@ -43,7 +43,7 @@ export default {
const event = new window.Event('change'); const event = new window.Event('change');
inputs[1].checked = true; inputs[1].checked = true;
inputs[1].dispatchEvent( event ); await inputs[1].dispatchEvent(event);
assert.equal(component.numCompleted, 2); assert.equal(component.numCompleted, 2);
assert.htmlEqual(target.innerHTML, ` assert.htmlEqual(target.innerHTML, `

@ -13,14 +13,14 @@ export default {
<p>true</p> <p>true</p>
`, `,
test ( assert, component, target, window ) { async test(assert, component, target, window) {
const input = target.querySelector('input'); const input = target.querySelector('input');
assert.equal(input.checked, true); assert.equal(input.checked, true);
const event = new window.Event('change'); const event = new window.Event('change');
input.checked = false; 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>`);

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

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

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

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

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

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

Loading…
Cancel
Save