mirror of https://github.com/sveltejs/svelte
[feature] Dynamic elements implementation <svelte:element> (#6898)
Closes #2324 Co-authored-by: Alfred Ringstad <alfred.ringstad@hyperlab.se> Co-authored-by: Simon Holthausen <simon.holthausen@accso.de> Co-authored-by: tanhauhau <lhtan93@gmail.com>pull/7434/head
parent
54197c5a1f
commit
e0d93254fd
@ -0,0 +1,18 @@
|
|||||||
|
<script>
|
||||||
|
const options = ['h1', 'h3', 'p'];
|
||||||
|
let selected = options[0];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<select bind:value={selected}>
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{#if selected === 'h1'}
|
||||||
|
<h1>I'm a h1 tag</h1>
|
||||||
|
{:else if selected === 'h3'}
|
||||||
|
<h3>I'm a h3 tag</h3>
|
||||||
|
{:else if selected === 'p'}
|
||||||
|
<p>I'm a p tag</p>
|
||||||
|
{/if}
|
@ -0,0 +1,12 @@
|
|||||||
|
<script>
|
||||||
|
const options = ['h1', 'h3', 'p'];
|
||||||
|
let selected = options[0];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<select bind:value={selected}>
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<svelte:element this={selected}>I'm a {selected} tag</svelte:element>
|
@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
title: <svelte:element>
|
||||||
|
---
|
||||||
|
|
||||||
|
Sometimes we don't know in advance what kind of DOM element to render. `<svelte:element>` comes in handy here. Instead of a sequence of `if` blocks...
|
||||||
|
|
||||||
|
```html
|
||||||
|
{#if selected === 'h1'}
|
||||||
|
<h1>I'm a h1 tag</h1>
|
||||||
|
{:else if selected === 'h3'}
|
||||||
|
<h3>I'm a h3 tag</h3>
|
||||||
|
{:else if selected === 'p'}
|
||||||
|
<p>I'm a p tag</p>
|
||||||
|
{/if}
|
||||||
|
```
|
||||||
|
|
||||||
|
...we can have a single dynamic component:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<svelte:element this={selected}>I'm a {selected} tag</svelte:element>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `this` value can be any string, or a falsy value — if it's falsy, no element is rendered.
|
@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
warnings: [
|
||||||
|
{
|
||||||
|
code: 'css-unused-selector',
|
||||||
|
end: {
|
||||||
|
character: 86,
|
||||||
|
column: 8,
|
||||||
|
line: 7
|
||||||
|
},
|
||||||
|
frame:
|
||||||
|
' 5: color: red;\n 6: }\n 7: .unused {\n ^\n 8: font-style: italic;\n 9: }',
|
||||||
|
message: 'Unused CSS selector ".unused"',
|
||||||
|
pos: 79,
|
||||||
|
start: {
|
||||||
|
character: 79,
|
||||||
|
column: 1,
|
||||||
|
line: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
.used.svelte-xyz{color:red}
|
@ -0,0 +1 @@
|
|||||||
|
<div class="used svelte-xyz"></div>
|
@ -0,0 +1,10 @@
|
|||||||
|
<svelte:element this="div" class="used" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.used {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.unused {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,2 @@
|
|||||||
|
<svelte:element this="div"></svelte:element>
|
||||||
|
<svelte:element this="div" class="foo"></svelte:element>
|
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"html": {
|
||||||
|
"start": 0,
|
||||||
|
"end": 101,
|
||||||
|
"type": "Fragment",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"start": 0,
|
||||||
|
"end": 44,
|
||||||
|
"type": "Element",
|
||||||
|
"name": "svelte:element",
|
||||||
|
"attributes": [],
|
||||||
|
"children": [],
|
||||||
|
"tag": "div"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Text",
|
||||||
|
"start": 44,
|
||||||
|
"end": 45,
|
||||||
|
"data": "\n",
|
||||||
|
"raw": "\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": 45,
|
||||||
|
"end": 101,
|
||||||
|
"type": "Element",
|
||||||
|
"name": "svelte:element",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"type": "Attribute",
|
||||||
|
"start": 72,
|
||||||
|
"end": 83,
|
||||||
|
"name": "class",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "Text",
|
||||||
|
"start": 79,
|
||||||
|
"end": 82,
|
||||||
|
"data": "foo",
|
||||||
|
"raw": "foo"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"children": [],
|
||||||
|
"tag": "div"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
<svelte:element this={tag}></svelte:element>
|
||||||
|
<svelte:element this={tag} class="foo"></svelte:element>
|
@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"html": {
|
||||||
|
"start": 0,
|
||||||
|
"end": 101,
|
||||||
|
"type": "Fragment",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"start": 0,
|
||||||
|
"end": 44,
|
||||||
|
"type": "Element",
|
||||||
|
"name": "svelte:element",
|
||||||
|
"attributes": [],
|
||||||
|
"children": [],
|
||||||
|
"tag": {
|
||||||
|
"start": 22,
|
||||||
|
"end": 25,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 22
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 25
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "tag",
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Text",
|
||||||
|
"start": 44,
|
||||||
|
"end": 45,
|
||||||
|
"data": "\n",
|
||||||
|
"raw": "\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": 45,
|
||||||
|
"end": 101,
|
||||||
|
"type": "Element",
|
||||||
|
"name": "svelte:element",
|
||||||
|
"children": [],
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"type": "Attribute",
|
||||||
|
"start": 72,
|
||||||
|
"end": 83,
|
||||||
|
"name": "class",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "Text",
|
||||||
|
"start": 79,
|
||||||
|
"end": 82,
|
||||||
|
"data": "foo",
|
||||||
|
"raw": "foo"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tag": {
|
||||||
|
"start": 67,
|
||||||
|
"end": 70,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 2,
|
||||||
|
"column": 22
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 2,
|
||||||
|
"column": 25
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "tag",
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
let logs = [];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
html: `
|
||||||
|
<h1>tag is h1.</h1>
|
||||||
|
`,
|
||||||
|
props: {
|
||||||
|
logs
|
||||||
|
},
|
||||||
|
after_test() {
|
||||||
|
logs = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
async test({ assert, component, target }) {
|
||||||
|
assert.equal(component.tag, 'h1');
|
||||||
|
|
||||||
|
assert.deepEqual(logs, ['create: h1,opt1']);
|
||||||
|
component.opt = 'opt2';
|
||||||
|
|
||||||
|
assert.equal(component.tag, 'h1');
|
||||||
|
assert.deepEqual(logs, ['create: h1,opt1', 'update: h1,opt2']);
|
||||||
|
|
||||||
|
component.tag = 'h2';
|
||||||
|
|
||||||
|
assert.equal(component.tag, 'h2');
|
||||||
|
assert.deepEqual(logs, [
|
||||||
|
'create: h1,opt1',
|
||||||
|
'update: h1,opt2',
|
||||||
|
'destroy',
|
||||||
|
'create: h2,opt2'
|
||||||
|
]);
|
||||||
|
assert.htmlEqual(target.innerHTML, '<h2>tag is h2.</h2>');
|
||||||
|
|
||||||
|
component.tag = false;
|
||||||
|
assert.deepEqual(logs, [
|
||||||
|
'create: h1,opt1',
|
||||||
|
'update: h1,opt2',
|
||||||
|
'destroy',
|
||||||
|
'create: h2,opt2',
|
||||||
|
'destroy'
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, '');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,14 @@
|
|||||||
|
<script>
|
||||||
|
export let logs = [];
|
||||||
|
export let tag = "h1";
|
||||||
|
export let opt = "opt1";
|
||||||
|
function foo(node, {tag, opt}) {
|
||||||
|
logs.push(`create: ${tag},${opt}`);
|
||||||
|
return {
|
||||||
|
update: ({tag, opt}) => logs.push(`update: ${tag},${opt}`),
|
||||||
|
destroy: () => logs.push('destroy'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag} use:foo={{tag, opt}}>tag is {tag}.</svelte:element>
|
@ -0,0 +1,105 @@
|
|||||||
|
let originalDivGetBoundingClientRect;
|
||||||
|
let originalSpanGetBoundingClientRect;
|
||||||
|
let originalParagraphGetBoundingClientRect;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
skip_if_ssr: true,
|
||||||
|
props: {
|
||||||
|
things: [
|
||||||
|
{ id: 1, name: 'a' },
|
||||||
|
{ id: 2, name: 'b' },
|
||||||
|
{ id: 3, name: 'c' },
|
||||||
|
{ id: 4, name: 'd' },
|
||||||
|
{ id: 5, name: 'e' }
|
||||||
|
],
|
||||||
|
tag: 'div'
|
||||||
|
},
|
||||||
|
|
||||||
|
html: `
|
||||||
|
<div>a</div>
|
||||||
|
<div>b</div>
|
||||||
|
<div>c</div>
|
||||||
|
<div>d</div>
|
||||||
|
<div>e</div>
|
||||||
|
`,
|
||||||
|
|
||||||
|
before_test() {
|
||||||
|
originalDivGetBoundingClientRect =
|
||||||
|
window.HTMLDivElement.prototype.getBoundingClientRect;
|
||||||
|
originalSpanGetBoundingClientRect =
|
||||||
|
window.HTMLSpanElement.prototype.getBoundingClientRect;
|
||||||
|
originalParagraphGetBoundingClientRect =
|
||||||
|
window.HTMLParagraphElement.prototype.getBoundingClientRect;
|
||||||
|
|
||||||
|
window.HTMLDivElement.prototype.getBoundingClientRect =
|
||||||
|
fakeGetBoundingClientRect;
|
||||||
|
window.HTMLSpanElement.prototype.getBoundingClientRect =
|
||||||
|
fakeGetBoundingClientRect;
|
||||||
|
window.HTMLParagraphElement.prototype.getBoundingClientRect =
|
||||||
|
fakeGetBoundingClientRect;
|
||||||
|
|
||||||
|
function fakeGetBoundingClientRect() {
|
||||||
|
const index = [...this.parentNode.children].indexOf(this);
|
||||||
|
const top = index * 30;
|
||||||
|
|
||||||
|
return {
|
||||||
|
left: 0,
|
||||||
|
right: 100,
|
||||||
|
top,
|
||||||
|
bottom: top + 20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
after_test() {
|
||||||
|
window.HTMLDivElement.prototype.getBoundingClientRect =
|
||||||
|
originalDivGetBoundingClientRect;
|
||||||
|
window.HTMLSpanElement.prototype.getBoundingClientRect =
|
||||||
|
originalSpanGetBoundingClientRect;
|
||||||
|
window.HTMLParagraphElement.prototype.getBoundingClientRect =
|
||||||
|
originalParagraphGetBoundingClientRect;
|
||||||
|
},
|
||||||
|
|
||||||
|
async test({ assert, component, target, raf }) {
|
||||||
|
// switch tag and things at the same time
|
||||||
|
await component.update('p', [
|
||||||
|
{ id: 5, name: 'e' },
|
||||||
|
{ id: 2, name: 'b' },
|
||||||
|
{ id: 3, name: 'c' },
|
||||||
|
{ id: 4, name: 'd' },
|
||||||
|
{ id: 1, name: 'a' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const ps = document.querySelectorAll('p');
|
||||||
|
assert.equal(ps[0].dy, 120);
|
||||||
|
assert.equal(ps[4].dy, -120);
|
||||||
|
|
||||||
|
raf.tick(50);
|
||||||
|
assert.equal(ps[0].dy, 60);
|
||||||
|
assert.equal(ps[4].dy, -60);
|
||||||
|
|
||||||
|
raf.tick(100);
|
||||||
|
assert.equal(ps[0].dy, 0);
|
||||||
|
assert.equal(ps[4].dy, 0);
|
||||||
|
|
||||||
|
await component.update('span', [
|
||||||
|
{ id: 1, name: 'a' },
|
||||||
|
{ id: 2, name: 'b' },
|
||||||
|
{ id: 3, name: 'c' },
|
||||||
|
{ id: 4, name: 'd' },
|
||||||
|
{ id: 5, name: 'e' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const spans = document.querySelectorAll('span');
|
||||||
|
|
||||||
|
assert.equal(spans[0].dy, 120);
|
||||||
|
assert.equal(spans[4].dy, -120);
|
||||||
|
|
||||||
|
raf.tick(150);
|
||||||
|
assert.equal(spans[0].dy, 60);
|
||||||
|
assert.equal(spans[4].dy, -60);
|
||||||
|
|
||||||
|
raf.tick(200);
|
||||||
|
assert.equal(spans[0].dy, 0);
|
||||||
|
assert.equal(spans[4].dy, 0);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
<script>
|
||||||
|
export let things;
|
||||||
|
export let tag;
|
||||||
|
|
||||||
|
function flip(node, animation, params) {
|
||||||
|
const dx = animation.from.left - animation.to.left;
|
||||||
|
const dy = animation.from.top - animation.to.top;
|
||||||
|
|
||||||
|
return {
|
||||||
|
duration: 100,
|
||||||
|
tick: (t, u) => {
|
||||||
|
node.dx = u * dx;
|
||||||
|
node.dy = u * dy;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update(new_tag, new_things) {
|
||||||
|
things = new_things;
|
||||||
|
tag = new_tag;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each things as thing (thing.id)}
|
||||||
|
<svelte:element this={tag} animate:flip>{thing.name}</svelte:element>
|
||||||
|
{/each}
|
@ -0,0 +1,62 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
things: [
|
||||||
|
{ id: 1, name: 'a' },
|
||||||
|
{ id: 2, name: 'b' },
|
||||||
|
{ id: 3, name: 'c' },
|
||||||
|
{ id: 4, name: 'd' },
|
||||||
|
{ id: 5, name: 'e' }
|
||||||
|
],
|
||||||
|
tag: 'div'
|
||||||
|
},
|
||||||
|
|
||||||
|
html: `
|
||||||
|
<div>a</div>
|
||||||
|
<div>b</div>
|
||||||
|
<div>c</div>
|
||||||
|
<div>d</div>
|
||||||
|
<div>e</div>
|
||||||
|
`,
|
||||||
|
|
||||||
|
test({ assert, component, target, raf }) {
|
||||||
|
component.tag = 'p';
|
||||||
|
assert.equal(target.querySelectorAll('p').length, 5);
|
||||||
|
|
||||||
|
component.tag = 'div';
|
||||||
|
let divs = target.querySelectorAll('div');
|
||||||
|
divs.forEach(div => {
|
||||||
|
div.getBoundingClientRect = function() {
|
||||||
|
const index = [...this.parentNode.children].indexOf(this);
|
||||||
|
const top = index * 30;
|
||||||
|
|
||||||
|
return {
|
||||||
|
left: 0,
|
||||||
|
right: 100,
|
||||||
|
top,
|
||||||
|
bottom: top + 20
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
component.things = [
|
||||||
|
{ id: 5, name: 'e' },
|
||||||
|
{ id: 2, name: 'b' },
|
||||||
|
{ id: 3, name: 'c' },
|
||||||
|
{ id: 4, name: 'd' },
|
||||||
|
{ id: 1, name: 'a' }
|
||||||
|
];
|
||||||
|
|
||||||
|
divs = target.querySelectorAll('div');
|
||||||
|
assert.ok(~divs[0].style.animation.indexOf('__svelte'));
|
||||||
|
assert.equal(divs[1].style.animation, '');
|
||||||
|
assert.equal(divs[2].style.animation, '');
|
||||||
|
assert.equal(divs[3].style.animation, '');
|
||||||
|
assert.ok(~divs[4].style.animation.indexOf('__svelte'));
|
||||||
|
|
||||||
|
raf.tick(100);
|
||||||
|
assert.deepEqual([
|
||||||
|
divs[0].style.animation,
|
||||||
|
divs[4].style.animation
|
||||||
|
], ['', '']);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,18 @@
|
|||||||
|
<script>
|
||||||
|
export let things;
|
||||||
|
export let tag;
|
||||||
|
|
||||||
|
function flip(node, animation, params) {
|
||||||
|
const dx = animation.from.left - animation.to.left;
|
||||||
|
const dy = animation.from.top - animation.to.top;
|
||||||
|
|
||||||
|
return {
|
||||||
|
duration: 100,
|
||||||
|
css: (t, u) => `transform: translate(${u + dx}px, ${u * dy}px)`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each things as thing (thing.id)}
|
||||||
|
<svelte:element this={tag} animate:flip>{thing.name}</svelte:element>
|
||||||
|
{/each}
|
@ -0,0 +1,17 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
tag: 'div'
|
||||||
|
},
|
||||||
|
html: '<div style="color: red;">Foo</div>',
|
||||||
|
|
||||||
|
test({ assert, component, target }) {
|
||||||
|
component.tag = 'h1';
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<h1 style="color: red;">Foo</h1>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
export let tag = 'div';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag} style="color: red;">Foo</svelte:element>
|
@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
error: "'value' is not a valid binding on <svelte:element> elements"
|
||||||
|
};
|
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
const tag = "div";
|
||||||
|
let value;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag} bind:value></svelte:element>
|
@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
html: '<div></div>',
|
||||||
|
|
||||||
|
test({ assert, component, target }) {
|
||||||
|
const div = target.querySelector('div');
|
||||||
|
assert.equal(div, component.foo);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
const tag = "div";
|
||||||
|
export let foo;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag} bind:this={foo}></svelte:element>
|
@ -0,0 +1,17 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
tag: 'div'
|
||||||
|
},
|
||||||
|
html: '<div>Foo</div>',
|
||||||
|
|
||||||
|
test({ assert, component, target }) {
|
||||||
|
component.tag = 'h1';
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<h1>Foo</h1>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
export let tag = 'div';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag}>Foo</svelte:element>
|
@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
html: ''
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
let tag = '';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag}>Foo</svelte:element>
|
@ -0,0 +1,21 @@
|
|||||||
|
let clicked = false;
|
||||||
|
function handler() {
|
||||||
|
clicked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
handler
|
||||||
|
},
|
||||||
|
html: '<button>Foo</button>',
|
||||||
|
|
||||||
|
test({ assert, target }) {
|
||||||
|
assert.equal(clicked, false);
|
||||||
|
|
||||||
|
const button = target.querySelector('button');
|
||||||
|
const click = new window.MouseEvent('click');
|
||||||
|
button.dispatchEvent(click);
|
||||||
|
|
||||||
|
assert.equal(clicked, true);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
const tag = "button";
|
||||||
|
export let handler;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag} on:click={handler}>Foo</svelte:element>
|
@ -0,0 +1,23 @@
|
|||||||
|
let clicked = false;
|
||||||
|
function handler() {
|
||||||
|
clicked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
tag: 'div',
|
||||||
|
handler
|
||||||
|
},
|
||||||
|
html: '<div>Foo</div>',
|
||||||
|
|
||||||
|
test({ assert, component, target }) {
|
||||||
|
assert.equal(clicked, false);
|
||||||
|
|
||||||
|
component.tag = 'button';
|
||||||
|
const button = target.querySelector('button');
|
||||||
|
const click = new window.MouseEvent('click');
|
||||||
|
button.dispatchEvent(click);
|
||||||
|
|
||||||
|
assert.equal(clicked, true);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
export let tag;
|
||||||
|
export let handler;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag} on:click={handler}>Foo</svelte:element>
|
@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
html: '<div>Foo</div>'
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
<svelte:element this={"div"}>Foo</svelte:element>
|
@ -0,0 +1,9 @@
|
|||||||
|
export default {
|
||||||
|
compileOptions: {
|
||||||
|
dev: true
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
tag: 123
|
||||||
|
},
|
||||||
|
error: '<svelte:element> expects "this" attribute to be a string.'
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
export let tag;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag} />
|
@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
html: ''
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
let tag = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag}>Foo</svelte:element>
|
@ -0,0 +1,16 @@
|
|||||||
|
let clicked = false;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
tag: 'div',
|
||||||
|
onClick: () => clicked = true
|
||||||
|
},
|
||||||
|
html: '<div style="display: inline;">Foo</div>',
|
||||||
|
|
||||||
|
async test({ assert, target, window }) {
|
||||||
|
const div = target.querySelector('div');
|
||||||
|
await div.dispatchEvent(new window.MouseEvent('click'));
|
||||||
|
|
||||||
|
assert.equal(clicked, true);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
const tag = "div";
|
||||||
|
export let onClick;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag} style="display: inline;" on:click={onClick}>Foo</svelte:element>
|
@ -0,0 +1,7 @@
|
|||||||
|
<h1>Foo</h1>
|
||||||
|
<div id="default">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div id="other">
|
||||||
|
<slot name='other'></slot>
|
||||||
|
</div>
|
@ -0,0 +1,29 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
x: true
|
||||||
|
},
|
||||||
|
|
||||||
|
html: `
|
||||||
|
<h1>Foo</h1>
|
||||||
|
<div id="default">
|
||||||
|
<h1>This is default slot</h1>
|
||||||
|
</div>
|
||||||
|
<div id="other">
|
||||||
|
<h1 slot='other'>This is other slot</h1>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
|
||||||
|
test({ assert, component, target }) {
|
||||||
|
component.tag = 'h2';
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, `
|
||||||
|
<h1>Foo</h1>
|
||||||
|
<div id="default">
|
||||||
|
<h2>This is default slot</h2>
|
||||||
|
</div>
|
||||||
|
<div id="other">
|
||||||
|
<h2 slot='other'>This is other slot</h2>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
import Foo from './Foo.svelte';
|
||||||
|
export let tag = "h1";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Foo>
|
||||||
|
<svelte:element this={tag}>This is default slot</svelte:element>
|
||||||
|
<svelte:element this={tag} slot='other'>This is other slot</svelte:element>
|
||||||
|
</Foo>
|
||||||
|
|
@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
html: '<div></div>'
|
||||||
|
};
|
@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
html: '<div>Foo</div>'
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
<svelte:element this="div">Foo</svelte:element>
|
@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
size: 1
|
||||||
|
},
|
||||||
|
html: '<h1>This is h1 tag</h1>',
|
||||||
|
|
||||||
|
test({ assert, component, target }) {
|
||||||
|
const h1 = target.firstChild;
|
||||||
|
component.size = 2;
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<h2>This is h2 tag</h2>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
const h2 = target.firstChild;
|
||||||
|
assert.notEqual(h1, h2);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
export let size;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this="{`h${size}`}">This is h{size} tag</svelte:element>
|
@ -0,0 +1,17 @@
|
|||||||
|
export default {
|
||||||
|
test({ assert, component, target, raf }) {
|
||||||
|
component.visible = true;
|
||||||
|
const h1 = target.querySelector('h1');
|
||||||
|
assert.equal(h1.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both');
|
||||||
|
|
||||||
|
raf.tick(150);
|
||||||
|
component.tag = 'h2';
|
||||||
|
const h2 = target.querySelector('h2');
|
||||||
|
assert.equal(h1.style.animation, '');
|
||||||
|
assert.equal(h2.style.animation, '');
|
||||||
|
|
||||||
|
raf.tick(50);
|
||||||
|
component.visible = false;
|
||||||
|
assert.equal(h2.style.animation, '__svelte_3750847757_0 100ms linear 0ms 1 both');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,17 @@
|
|||||||
|
<script>
|
||||||
|
export let tag = "h1";
|
||||||
|
export let visible;
|
||||||
|
|
||||||
|
function foo() {
|
||||||
|
return {
|
||||||
|
duration: 100,
|
||||||
|
css: t => {
|
||||||
|
return `opacity: ${t}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if visible}
|
||||||
|
<svelte:element this={tag} transition:foo></svelte:element>
|
||||||
|
{/if}
|
@ -0,0 +1,19 @@
|
|||||||
|
export default {
|
||||||
|
html: '',
|
||||||
|
test({ component, target, assert }) {
|
||||||
|
component.tag = 'h1';
|
||||||
|
assert.htmlEqual(target.innerHTML, '<h1>Foo</h1>');
|
||||||
|
|
||||||
|
component.tag = null;
|
||||||
|
assert.htmlEqual(target.innerHTML, '');
|
||||||
|
|
||||||
|
component.tag = 'div';
|
||||||
|
assert.htmlEqual(target.innerHTML, '<div>Foo</div>');
|
||||||
|
|
||||||
|
component.tag = false;
|
||||||
|
assert.htmlEqual(target.innerHTML, '');
|
||||||
|
|
||||||
|
component.tag = 'span';
|
||||||
|
assert.htmlEqual(target.innerHTML, '<span>Foo</span>');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
export let tag;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag}>Foo</svelte:element>
|
@ -0,0 +1,20 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
tag: 'div',
|
||||||
|
text: 'Foo'
|
||||||
|
},
|
||||||
|
html: '<div>Foo</div>',
|
||||||
|
|
||||||
|
test({ assert, component, target }) {
|
||||||
|
const div = target.firstChild;
|
||||||
|
component.tag = 'nav';
|
||||||
|
component.text = 'Bar';
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, `
|
||||||
|
<nav>Bar</nav>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const h1 = target.firstChild;
|
||||||
|
assert.notEqual(div, h1);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
export let tag = "div";
|
||||||
|
export let text = "Foo";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag}>{text}</svelte:element>
|
@ -0,0 +1 @@
|
|||||||
|
<div>Foo</div>
|
@ -0,0 +1 @@
|
|||||||
|
<svelte:element this="div">Foo</svelte:element>
|
@ -0,0 +1,2 @@
|
|||||||
|
<h1>Foo</h1>
|
||||||
|
<div>Bar</div>
|
@ -0,0 +1,7 @@
|
|||||||
|
<script>
|
||||||
|
let heading = 'h1';
|
||||||
|
let tag = 'div';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={heading}>Foo</svelte:element>
|
||||||
|
<svelte:element this={tag}>Bar</svelte:element>
|
@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"message": "Invalid element definition",
|
||||||
|
"code": "invalid-element-definition",
|
||||||
|
"start": {
|
||||||
|
"line": 2,
|
||||||
|
"column": 17,
|
||||||
|
"character": 23
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 2,
|
||||||
|
"column": 17,
|
||||||
|
"character": 23
|
||||||
|
},
|
||||||
|
"pos": 23
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
<svelte:element this>foo</svelte:element>
|
||||||
|
</div>
|
@ -0,0 +1,15 @@
|
|||||||
|
[{
|
||||||
|
"code": "missing-element-definition",
|
||||||
|
"message": "<svelte:element> must have a 'this' attribute",
|
||||||
|
"start": {
|
||||||
|
"line": 2,
|
||||||
|
"column": 1,
|
||||||
|
"character": 7
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 2,
|
||||||
|
"column": 1,
|
||||||
|
"character": 7
|
||||||
|
},
|
||||||
|
"pos": 7
|
||||||
|
}]
|
@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
<svelte:element>foo</svelte:element>
|
||||||
|
</div>
|
@ -0,0 +1,15 @@
|
|||||||
|
[{
|
||||||
|
"code": "unexpected-reserved-word",
|
||||||
|
"message": "'this' is a reserved word in JavaScript and cannot be used here",
|
||||||
|
"start": {
|
||||||
|
"line": 2,
|
||||||
|
"column": 18,
|
||||||
|
"character": 24
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 2,
|
||||||
|
"column": 18,
|
||||||
|
"character": 24
|
||||||
|
},
|
||||||
|
"pos": 24
|
||||||
|
}]
|
@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
<svelte:element {this}>foo</svelte:element>
|
||||||
|
</div>
|
Loading…
Reference in new issue