From 80ea0572e2efba3f65789c50f5afe6c0ef334459 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 13 Nov 2018 23:23:01 -0500 Subject: [PATCH] a few more tests --- src/compile/Component.ts | 74 +++++++++++++++---- src/compile/render-dom/index.ts | 10 ++- src/internal/SvelteComponent.js | 20 +++-- src/parse/state/tag.ts | 2 +- src/utils/annotateWithScopes.ts | 2 +- test/runtime/index.js | 19 +++-- .../action-ternary-template/_config.js | 16 ++-- .../samples/action-ternary-template/main.html | 1 - test/runtime/samples/action-this/_config.js | 15 ++-- test/runtime/samples/action-this/main.html | 8 +- test/runtime/samples/action-update/_config.js | 36 ++++----- test/runtime/samples/action-update/main.html | 4 +- 12 files changed, 132 insertions(+), 75 deletions(-) diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 45db4e0e93..bef3dfe1e9 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -10,7 +10,7 @@ import namespaces from '../utils/namespaces'; import { removeNode } from '../utils/removeNode'; import nodeToString from '../utils/nodeToString'; import wrapModule from './wrapModule'; -import { createScopes } from '../utils/annotateWithScopes'; +import { createScopes, extractNames } from '../utils/annotateWithScopes'; import getName from '../utils/getName'; import Stylesheet from './css/Stylesheet'; import { test } from '../config'; @@ -92,6 +92,7 @@ function increaseIndentation( // the wrong idea about the shape of each/if blocks childKeys.EachBlock = childKeys.IfBlock = ['children', 'else']; childKeys.Attribute = ['value']; +childKeys.ExportNamedDeclaration = ['declaration', 'specifiers']; export default class Component { stats: Stats; @@ -104,7 +105,6 @@ export default class Component { customElement: CustomElementOptions; tag: string; - props: string[]; properties: Map; @@ -123,7 +123,7 @@ export default class Component { hasComponents: boolean; computations: Computation[]; templateProperties: Record; - javascript: [string, string]; + javascript: string; used: { components: Set; @@ -135,6 +135,8 @@ export default class Component { }; declarations: string[]; + exports: Array<{ name: string, as: string }>; + props: string[]; refCallees: Node[]; @@ -193,6 +195,7 @@ export default class Component { }; this.declarations = []; + this.exports = []; this.refs = new Set(); this.refCallees = []; @@ -509,10 +512,15 @@ export default class Component { const { code, source, imports } = this; - const indentationLevel = getIndentationLevel(source, js.content.body[0].start); - const indentExclusionRanges = getIndentExclusionRanges(js.content); + const indent = code.getIndentString(); + code.indent(indent, { + exclude: [ + [0, js.content.start], + [js.content.end, source.length] + ] + }); - const { scope, globals } = createScopes(js.content); + let { scope, map, globals } = createScopes(js.content); scope.declarations.forEach(name => { this.userVars.add(name); @@ -533,6 +541,30 @@ export default class Component { }) } + if (node.type === 'ExportNamedDeclaration') { + if (node.declaration) { + if (node.declaration.type === 'VariableDeclaration') { + node.declaration.declarations.forEach(declarator => { + extractNames(declarator.id).forEach(name => { + this.exports.push({ name, as: name }); + }); + }); + } else { + const { name } = node.declaration.id; + this.exports.push({ name, as: name }); + } + + code.remove(node.start, node.declaration.start); + } else { + node.specifiers.forEach(specifier => { + this.exports.push({ + name: specifier.local.name, + as: specifier.exported.name + }); + }); + } + } + // imports need to be hoisted out of the IIFE // TODO hoist other stuff where possible else if (node.type === 'ImportDeclaration') { @@ -545,20 +577,32 @@ export default class Component { } }); + walk(js.content, { + enter(node) { + if (map.has(node)) { + scope = map.get(node); + } + + if (node.type === 'AssignmentExpression') { + const { name } = flattenReference(node.left); + code.appendLeft(node.end, `; __make_dirty('${name}')`); + } + + }, + + leave(node) { + if (map.has(node)) { + scope = scope.parent; + } + } + }); + let a = js.content.start; while (/\s/.test(source[a])) a += 1; let b = js.content.end; while (/\s/.test(source[b - 1])) b -= 1; - this.javascript = this.defaultExport - ? [ - a !== this.defaultExport.start ? `[✂${a}-${this.defaultExport.start}✂]` : '', - b !== this.defaultExport.end ?`[✂${this.defaultExport.end}-${b}✂]` : '' - ] - : [ - a !== b ? `[✂${a}-${b}✂]` : '', - '' - ]; + this.javascript = a !== b ? `[✂${a}-${b}✂]` : ''; } } diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts index b92f86a12a..a04248ff5d 100644 --- a/src/compile/render-dom/index.ts +++ b/src/compile/render-dom/index.ts @@ -13,7 +13,6 @@ export default function dom( const format = options.format || 'es'; const { - computations, name, templateProperties } = component; @@ -110,9 +109,16 @@ export default function dom( } else { builder.addBlock(deindent` class ${name} extends @SvelteComponent { - __init() { + __init(__set_inject_props, __set_inject_refs, __make_dirty) { ${component.javascript} + __set_inject_props(props => { + // TODO only do this for export let|var + ${(component.exports.map(name => + `if ('${name.as}' in props) ${name.as} = props.${name.as};` + ))} + }); + return () => ({ ${(component.declarations).join(', ')} }); } diff --git a/src/internal/SvelteComponent.js b/src/internal/SvelteComponent.js index bacbee4d8c..a04094df02 100644 --- a/src/internal/SvelteComponent.js +++ b/src/internal/SvelteComponent.js @@ -1,9 +1,11 @@ -import { schedule_update } from './scheduler'; +import { schedule_update, flush } from './scheduler'; export class SvelteComponent { constructor(options) { this.__get_state = this.__init( - fn => this.__inject_props = fn + fn => this.__inject_props = fn, + fn => this.__inject_refs = fn, + key => this.__make_dirty(key) ); this.__dirty = null; @@ -14,6 +16,7 @@ export class SvelteComponent { if (options.target) { this.__mount(options.target); + flush(); } } @@ -25,10 +28,12 @@ export class SvelteComponent { this.__destroy(true); } - __make_dirty() { - if (this.__dirty) return; - this.__dirty = {}; - schedule_update(this); + __make_dirty(key) { + if (!this.__dirty) { + schedule_update(this); + this.__dirty = {}; + } + this.__dirty[key] = true; } __mount(target, anchor) { @@ -39,8 +44,7 @@ export class SvelteComponent { __set(key, value) { this.__inject_props({ [key]: value }); - this.__make_dirty(); - this.__dirty[key] = true; + this.__make_dirty(key); } __update() { diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index 8aba3dcc83..dcb5530fd7 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -398,7 +398,7 @@ function readAttribute(parser: Parser, uniqueNames: Set) { end, type, name, - expression: value[0].expression + expression: value[0] && value[0].expression }; } diff --git a/src/utils/annotateWithScopes.ts b/src/utils/annotateWithScopes.ts index 4771b4660f..33f083ac5f 100644 --- a/src/utils/annotateWithScopes.ts +++ b/src/utils/annotateWithScopes.ts @@ -83,7 +83,7 @@ export class Scope { } } -function extractNames(param: Node) { +export function extractNames(param: Node) { const names: string[] = []; extractors[param.type](names, param); return names; diff --git a/test/runtime/index.js b/test/runtime/index.js index 530f5d85cc..ddbb27db5d 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -46,16 +46,18 @@ describe.only("runtime", () => { const failed = new Set(); - function runTest(dir, shared, hydrate) { + function runTest(dir, internal, hydrate) { if (dir[0] === ".") return; + const { flush } = require(internal); + const config = loadConfig(`./runtime/samples/${dir}/_config.js`); if (config.solo && process.env.CI) { throw new Error("Forgot to remove `solo: true` from test"); } - (config.skip ? it.skip : config.solo ? it.only : it)(`${dir} (${shared ? 'shared' : 'inline'} helpers${hydrate ? ', hydration' : ''})`, () => { + (config.skip ? it.skip : config.solo ? it.only : it)(`${dir} ${hydrate ? '(with hydration)' : ''}`, () => { if (failed.has(dir)) { // this makes debugging easier, by only printing compiled output once throw new Error('skipping test, already failed'); @@ -67,7 +69,7 @@ describe.only("runtime", () => { global.document.title = ''; compileOptions = config.compileOptions || {}; - compileOptions.shared = shared; + compileOptions.shared = internal; compileOptions.hydratable = hydrate; compileOptions.store = !!config.store; compileOptions.immutable = config.immutable; @@ -114,7 +116,7 @@ describe.only("runtime", () => { try { SvelteComponent = require(`./samples/${dir}/main.html`); } catch (err) { - showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store, skipIntroByDefault: compileOptions.skipIntroByDefault, nestedTransitions: compileOptions.nestedTransitions }, compile); // eslint-disable-line no-console + showOutput(cwd, { internal, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store, skipIntroByDefault: compileOptions.skipIntroByDefault, nestedTransitions: compileOptions.nestedTransitions }, compile); // eslint-disable-line no-console throw err; } @@ -134,8 +136,7 @@ describe.only("runtime", () => { const options = Object.assign({}, { target, hydrate, - data: config.data, - store: (config.store !== true && config.store), + props: config.props, intro: config.intro }, config.options || {}); @@ -178,7 +179,7 @@ describe.only("runtime", () => { } else { failed.add(dir); showOutput(cwd, { - shared, + internal, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store, @@ -190,7 +191,9 @@ describe.only("runtime", () => { } }) .then(() => { - if (config.show) showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store, skipIntroByDefault: compileOptions.skipIntroByDefault, nestedTransitions: compileOptions.nestedTransitions }, compile); + if (config.show) showOutput(cwd, { internal, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store, skipIntroByDefault: compileOptions.skipIntroByDefault, nestedTransitions: compileOptions.nestedTransitions }, compile); + + flush(); }); }); } diff --git a/test/runtime/samples/action-ternary-template/_config.js b/test/runtime/samples/action-ternary-template/_config.js index 7e9196d595..53d83b5958 100644 --- a/test/runtime/samples/action-ternary-template/_config.js +++ b/test/runtime/samples/action-ternary-template/_config.js @@ -1,5 +1,5 @@ export default { - data: { + props: { target: 'World!', display: true, }, @@ -8,13 +8,13 @@ export default {

`, - test ( assert, component, target, window ) { - const header = target.querySelector( 'h1' ); - const eventClick = new window.MouseEvent( 'click' ); + test(assert, component, target, window) { + const header = target.querySelector('h1'); + const click = new window.MouseEvent('click'); - header.dispatchEvent( eventClick ); - assert.htmlEqual( target.innerHTML, ` + header.dispatchEvent(click); + assert.htmlEqual(target.innerHTML, `

Hello World!

- ` ); - } + `); + }, }; diff --git a/test/runtime/samples/action-ternary-template/main.html b/test/runtime/samples/action-ternary-template/main.html index bec7e0a99b..875ffd71a8 100644 --- a/test/runtime/samples/action-ternary-template/main.html +++ b/test/runtime/samples/action-ternary-template/main.html @@ -3,7 +3,6 @@ export let target; function insert(node, text) { - function onClick() { node.textContent = text; } diff --git a/test/runtime/samples/action-this/_config.js b/test/runtime/samples/action-this/_config.js index df204a7a61..e3df5947e6 100644 --- a/test/runtime/samples/action-this/_config.js +++ b/test/runtime/samples/action-this/_config.js @@ -1,10 +1,11 @@ export default { - test ( assert, component, target, window ) { - const button = target.querySelector( 'button' ); - const click = new window.MouseEvent( 'click' ); + async test(assert, component, target, window) { + const button = target.querySelector('button'); + const click = new window.MouseEvent('click'); - assert.htmlEqual( target.innerHTML, `` ); - button.dispatchEvent( click ); - assert.htmlEqual( target.innerHTML, `` ); - } + assert.htmlEqual(target.innerHTML, ``); + await button.dispatchEvent(click); + await Promise.resolve(); + assert.htmlEqual(target.innerHTML, ``); + }, }; diff --git a/test/runtime/samples/action-this/main.html b/test/runtime/samples/action-this/main.html index 130d797aca..28d5444b32 100644 --- a/test/runtime/samples/action-this/main.html +++ b/test/runtime/samples/action-this/main.html @@ -1,10 +1,10 @@ - + \ No newline at end of file