mirror of https://github.com/sveltejs/svelte
commit
b45fe8020b
@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
let selection = '';
|
||||||
|
|
||||||
|
const handleSelectionChange = (e) => selection = document.getSelection();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:document on:selectionchange={handleSelectionChange} />
|
||||||
|
|
||||||
|
<p>Select this text to fire events</p>
|
||||||
|
<p>Selection: {selection}</p>
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"title": "<svelte:document>"
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
title: <svelte:body>
|
|
||||||
---
|
|
||||||
|
|
||||||
Similar to `<svelte:window>`, the `<svelte:body>` element allows you to listen for events that fire on `document.body`. This is useful with the `mouseenter` and `mouseleave` events, which don't fire on `window`.
|
|
||||||
|
|
||||||
Add the `mouseenter` and `mouseleave` handlers to the `<svelte:body>` tag:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<svelte:body
|
|
||||||
on:mouseenter={handleMouseenter}
|
|
||||||
on:mouseleave={handleMouseleave}
|
|
||||||
/>
|
|
||||||
```
|
|
@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
let selection = '';
|
||||||
|
|
||||||
|
const handleSelectionChange = (e) => selection = document.getSelection();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:body />
|
||||||
|
|
||||||
|
<p>Select this text to fire events</p>
|
||||||
|
<p>Selection: {selection}</p>
|
@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
let selection = '';
|
||||||
|
|
||||||
|
const handleSelectionChange = (e) => selection = document.getSelection();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:document on:selectionchange={handleSelectionChange} />
|
||||||
|
|
||||||
|
<p>Select this text to fire events</p>
|
||||||
|
<p>Selection: {selection}</p>
|
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
title: <svelte:document>
|
||||||
|
---
|
||||||
|
|
||||||
|
Similar to `<svelte:window>`, the `<svelte:document>` element allows you to listen for events that fire on `document`. This is useful with events like `selectionchange`, which doesn't fire on `window`.
|
||||||
|
|
||||||
|
Add the `selectionchange` handler to the `<svelte:document>` tag:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<svelte:document on:selectionchange={handleSelectionChange} />
|
||||||
|
```
|
||||||
|
|
||||||
|
> Avoid `mouseenter` and `mouseleave` handlers on this element, these events are not fired on `document` in all browsers. Use `<svelte:body>` for this instead.
|
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
title: <svelte:body>
|
||||||
|
---
|
||||||
|
|
||||||
|
Similar to `<svelte:window>` and `<svelte:document>`, the `<svelte:body>` element allows you to listen for events that fire on `document.body`. This is useful with the `mouseenter` and `mouseleave` events, which don't fire on `window`.
|
||||||
|
|
||||||
|
Add the `mouseenter` and `mouseleave` handlers to the `<svelte:body>` tag:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<svelte:body
|
||||||
|
on:mouseenter={handleMouseenter}
|
||||||
|
on:mouseleave={handleMouseleave}
|
||||||
|
/>
|
||||||
|
```
|
@ -0,0 +1,41 @@
|
|||||||
|
import Node from './shared/Node';
|
||||||
|
import EventHandler from './EventHandler';
|
||||||
|
import Action from './Action';
|
||||||
|
import Component from '../Component';
|
||||||
|
import TemplateScope from './shared/TemplateScope';
|
||||||
|
import { Element } from '../../interfaces';
|
||||||
|
import compiler_warnings from '../compiler_warnings';
|
||||||
|
|
||||||
|
export default class Document extends Node {
|
||||||
|
type: 'Document';
|
||||||
|
handlers: EventHandler[] = [];
|
||||||
|
actions: Action[] = [];
|
||||||
|
|
||||||
|
constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) {
|
||||||
|
super(component, parent, scope, info);
|
||||||
|
|
||||||
|
info.attributes.forEach((node) => {
|
||||||
|
if (node.type === 'EventHandler') {
|
||||||
|
this.handlers.push(new EventHandler(component, this, scope, node));
|
||||||
|
} else if (node.type === 'Action') {
|
||||||
|
this.actions.push(new Action(component, this, scope, node));
|
||||||
|
} else {
|
||||||
|
// TODO there shouldn't be anything else here...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private validate() {
|
||||||
|
const handlers_map = new Set();
|
||||||
|
|
||||||
|
this.handlers.forEach(handler => (
|
||||||
|
handlers_map.add(handler.name)
|
||||||
|
));
|
||||||
|
|
||||||
|
if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) {
|
||||||
|
this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import Block from '../Block';
|
||||||
|
import Wrapper from './shared/Wrapper';
|
||||||
|
import { x } from 'code-red';
|
||||||
|
import Document from '../../nodes/Document';
|
||||||
|
import { Identifier } from 'estree';
|
||||||
|
import EventHandler from './Element/EventHandler';
|
||||||
|
import add_event_handlers from './shared/add_event_handlers';
|
||||||
|
import { TemplateNode } from '../../../interfaces';
|
||||||
|
import Renderer from '../Renderer';
|
||||||
|
import add_actions from './shared/add_actions';
|
||||||
|
|
||||||
|
export default class DocumentWrapper extends Wrapper {
|
||||||
|
node: Document;
|
||||||
|
handlers: EventHandler[];
|
||||||
|
|
||||||
|
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
|
||||||
|
super(renderer, block, parent, node);
|
||||||
|
this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
|
||||||
|
add_event_handlers(block, x`@_document`, this.handlers);
|
||||||
|
add_actions(block, x`@_document`, this.node.actions);
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +1,33 @@
|
|||||||
import ConstTag from '../../../nodes/ConstTag';
|
import ConstTag from '../../../nodes/ConstTag';
|
||||||
import Block from '../../Block';
|
import Block from '../../Block';
|
||||||
import { b, x } from 'code-red';
|
import { b, Node, x } from 'code-red';
|
||||||
import Renderer from '../../Renderer';
|
import Renderer from '../../Renderer';
|
||||||
|
import Expression from '../../../nodes/shared/Expression';
|
||||||
|
|
||||||
export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string) {
|
export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string) {
|
||||||
const const_tags_props = [];
|
const const_tags_props = [];
|
||||||
const_tags.forEach((const_tag, i) => {
|
const_tags.forEach((const_tag, i) => {
|
||||||
const name = `#constants_${i}`;
|
const name = `#constants_${i}`;
|
||||||
const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`);
|
const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`);
|
||||||
const_tag.contexts.forEach(context => {
|
const to_ctx = (name: string) => block.renderer.context_lookup.has(name) ? x`${ctx}[${block.renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name } as Node;
|
||||||
const_tags_props.push(b`${ctx}[${block.renderer.context_lookup.get(context.key.name).index}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), name => block.renderer.context_lookup.has(name) ? x`${ctx}[${block.renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name })};`);
|
|
||||||
});
|
const_tag.contexts.forEach(context => {
|
||||||
});
|
if (context.type === 'DestructuredVariable') {
|
||||||
return const_tags_props;
|
const_tags_props.push(b`${ctx}[${block.renderer.context_lookup.get(context.key.name).index}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), to_ctx)}`);
|
||||||
|
} else {
|
||||||
|
const expression = new Expression(block.renderer.component, const_tag, const_tag.scope, context.key);
|
||||||
|
const_tags_props.push(b`const ${context.property_name} = ${expression.manipulate(block, ctx)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return const_tags_props;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function add_const_tags_context(renderer: Renderer, const_tags: ConstTag[]) {
|
export function add_const_tags_context(renderer: Renderer, const_tags: ConstTag[]) {
|
||||||
const_tags.forEach(const_tag => {
|
const_tags.forEach(const_tag => {
|
||||||
const_tag.contexts.forEach(context => {
|
const_tag.contexts.forEach(context => {
|
||||||
renderer.add_to_context(context.key.name, true);
|
if (context.type !== 'DestructuredVariable') return;
|
||||||
});
|
renderer.add_to_context(context.key.name, true);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
// Utilities for managing contenteditable nodes
|
||||||
|
import Attribute from '../nodes/Attribute';
|
||||||
|
import Element from '../nodes/Element';
|
||||||
|
|
||||||
|
export const CONTENTEDITABLE_BINDINGS = [
|
||||||
|
'textContent',
|
||||||
|
'innerHTML',
|
||||||
|
'innerText'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if node is an 'input' or 'textarea'.
|
||||||
|
* @param {Element} node The element to be checked
|
||||||
|
*/
|
||||||
|
function is_input_or_textarea(node: Element): boolean {
|
||||||
|
return node.name === 'textarea' || node.name === 'input';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given attribute is 'contenteditable'.
|
||||||
|
* @param {Attribute} attribute A node.attribute
|
||||||
|
*/
|
||||||
|
function is_attr_contenteditable(attribute: Attribute): boolean {
|
||||||
|
return attribute.name === 'contenteditable';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any of a node's attributes are 'contentenditable'.
|
||||||
|
* @param {Element} node The element to be checked
|
||||||
|
*/
|
||||||
|
export function has_contenteditable_attr(node: Element): boolean {
|
||||||
|
return node.attributes.some(is_attr_contenteditable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if node is not textarea or input, but has 'contenteditable' attribute.
|
||||||
|
* @param {Element} node The element to be tested
|
||||||
|
*/
|
||||||
|
export function is_contenteditable(node: Element): boolean {
|
||||||
|
return !is_input_or_textarea(node) && has_contenteditable_attr(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a given binding/node is contenteditable.
|
||||||
|
* @param {string} name A binding or node name to be checked
|
||||||
|
*/
|
||||||
|
export function is_name_contenteditable(name: string): boolean {
|
||||||
|
return CONTENTEDITABLE_BINDINGS.includes(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contenteditable attribute from the node (if it exists).
|
||||||
|
* @param {Element} node The element to get the attribute from
|
||||||
|
*/
|
||||||
|
export function get_contenteditable_attr(node: Element): Attribute | undefined {
|
||||||
|
return node.attributes.find(is_attr_contenteditable);
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/* generated by Svelte vX.Y.Z */
|
||||||
|
import { SvelteComponent, init, safe_not_equal } from "svelte/internal";
|
||||||
|
|
||||||
|
function instance($$self) {
|
||||||
|
class A {
|
||||||
|
p1;
|
||||||
|
p2 = 1;
|
||||||
|
#p3 = 2;
|
||||||
|
|
||||||
|
#getP3() {
|
||||||
|
return this.#p3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class Component extends SvelteComponent {
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
init(this, options, instance, null, safe_not_equal, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Component;
|
@ -0,0 +1,12 @@
|
|||||||
|
<script>
|
||||||
|
class A {
|
||||||
|
p1;
|
||||||
|
p2 = 1;
|
||||||
|
|
||||||
|
#p3 = 2;
|
||||||
|
|
||||||
|
#getP3() {
|
||||||
|
return this.#p3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,60 +0,0 @@
|
|||||||
/* generated by Svelte vX.Y.Z */
|
|
||||||
import {
|
|
||||||
SvelteComponent,
|
|
||||||
detach,
|
|
||||||
element,
|
|
||||||
init,
|
|
||||||
insert,
|
|
||||||
noop,
|
|
||||||
safe_not_equal,
|
|
||||||
set_data,
|
|
||||||
space,
|
|
||||||
text
|
|
||||||
} from "svelte/internal";
|
|
||||||
|
|
||||||
function create_fragment(ctx) {
|
|
||||||
let h1;
|
|
||||||
let t3;
|
|
||||||
let t4;
|
|
||||||
|
|
||||||
return {
|
|
||||||
c() {
|
|
||||||
h1 = element("h1");
|
|
||||||
h1.textContent = `Hello ${name}!`;
|
|
||||||
t3 = space();
|
|
||||||
t4 = text(/*foo*/ ctx[0]);
|
|
||||||
},
|
|
||||||
m(target, anchor) {
|
|
||||||
insert(target, h1, anchor);
|
|
||||||
insert(target, t3, anchor);
|
|
||||||
insert(target, t4, anchor);
|
|
||||||
},
|
|
||||||
p(ctx, [dirty]) {
|
|
||||||
if (dirty & /*foo*/ 1) set_data(t4, /*foo*/ ctx[0]);
|
|
||||||
},
|
|
||||||
i: noop,
|
|
||||||
o: noop,
|
|
||||||
d(detaching) {
|
|
||||||
if (detaching) detach(h1);
|
|
||||||
if (detaching) detach(t3);
|
|
||||||
if (detaching) detach(t4);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = 'world';
|
|
||||||
|
|
||||||
function instance($$self) {
|
|
||||||
let foo;
|
|
||||||
$: foo = name + name;
|
|
||||||
return [foo];
|
|
||||||
}
|
|
||||||
|
|
||||||
class Component extends SvelteComponent {
|
|
||||||
constructor(options) {
|
|
||||||
super();
|
|
||||||
init(this, options, instance, create_fragment, safe_not_equal, {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Component;
|
|
@ -1,7 +0,0 @@
|
|||||||
<script>
|
|
||||||
let name = 'world';
|
|
||||||
$: foo = name + name;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Hello {name}!</h1>
|
|
||||||
{foo}
|
|
@ -0,0 +1,24 @@
|
|||||||
|
export default {
|
||||||
|
html: `
|
||||||
|
<p style="font-size: 32px; color: red; background-color: green; border-color: green;"></p>
|
||||||
|
`,
|
||||||
|
|
||||||
|
test({ assert, target, window, component }) {
|
||||||
|
const p = target.querySelector('p');
|
||||||
|
const styles = window.getComputedStyle(p);
|
||||||
|
assert.equal(styles.color, 'rgb(255, 0, 0)');
|
||||||
|
assert.equal(styles.fontSize, '32px');
|
||||||
|
assert.equal(styles.backgroundColor, 'rgb(0, 128, 0)');
|
||||||
|
assert.equal(styles.borderColor, 'rgb(0, 128, 0)');
|
||||||
|
|
||||||
|
component.foo = 'font-size: 50px; color: green;'; // Update style attribute
|
||||||
|
{
|
||||||
|
const p = target.querySelector('p');
|
||||||
|
const styles = window.getComputedStyle(p);
|
||||||
|
assert.equal(styles.color, 'rgb(255, 0, 0)');
|
||||||
|
assert.equal(styles.fontSize, '32px');
|
||||||
|
assert.equal(styles.backgroundColor, 'rgb(0, 128, 0)');
|
||||||
|
assert.equal(styles.borderColor, 'rgb(0, 128, 0)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
export let foo = "font-size: 20px; color: blue;";
|
||||||
|
let baz = "red"; // static value
|
||||||
|
let bar = "32"; // static value interpolated
|
||||||
|
export let bg = "gre"; // dynamic value interpolated/cached
|
||||||
|
export let borderColor = "green"; // dynamic value non-cached
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p
|
||||||
|
style:font-size="{bar}px"
|
||||||
|
style:color={baz}
|
||||||
|
style="{foo}"
|
||||||
|
style:background-color="{bg}en"
|
||||||
|
style:border-color={borderColor}
|
||||||
|
/>
|
@ -0,0 +1,14 @@
|
|||||||
|
export default {
|
||||||
|
html: '<div></div>',
|
||||||
|
|
||||||
|
async test({ assert, target, window }) {
|
||||||
|
const visibility = new window.Event('visibilitychange');
|
||||||
|
|
||||||
|
await window.document.dispatchEvent(visibility);
|
||||||
|
assert.htmlEqual(target.innerHTML, `
|
||||||
|
<div>
|
||||||
|
<div class="tooltip">Perform an Action</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
<script>
|
||||||
|
let container;
|
||||||
|
function tooltip(node, text) {
|
||||||
|
let tooltip = null;
|
||||||
|
|
||||||
|
function onVisibilityChange() {
|
||||||
|
tooltip = document.createElement('div');
|
||||||
|
tooltip.classList.add('tooltip');
|
||||||
|
tooltip.textContent = text;
|
||||||
|
container.appendChild(tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.addEventListener('visibilitychange', onVisibilityChange);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
node.removeEventListener('visibilitychange', onVisibilityChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:document use:tooltip="{'Perform an Action'}" />
|
||||||
|
<div bind:this={container} />
|
@ -0,0 +1,32 @@
|
|||||||
|
export default {
|
||||||
|
async test({ assert, component, target }) {
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<p>propA: 3</p>
|
||||||
|
<p>propB: 7</p>
|
||||||
|
<p>num: 3</p>
|
||||||
|
<p>rest: {"prop3":{"prop9":9,"prop10":10}}</p>
|
||||||
|
<p>propZ: 5</p>
|
||||||
|
<p>propY: 6</p>
|
||||||
|
<p>rest: {"propX":7,"propW":8}</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await (component.object = Promise.resolve({ prop1: 'one', prop2: 'two', prop3: { prop7: 'seven' }, prop4: { prop10: 'ten' }}));
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<p>propA: seven</p>
|
||||||
|
<p>propB: ten</p>
|
||||||
|
<p>num: 5</p>
|
||||||
|
<p>rest: {"prop1":"one","prop2":"two"}</p>
|
||||||
|
<p>propZ: 5</p>
|
||||||
|
<p>propY: 6</p>
|
||||||
|
<p>rest: {"propX":7,"propW":8}</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
<script>
|
||||||
|
export let object = Promise.resolve({ prop1: { prop4: 2, prop5: 3 }, prop2: { prop6: 5, prop7: 6, prop8: 7 }, prop3: { prop9: 9, prop10: 10 } });
|
||||||
|
const objectReject = Promise.reject({ propZ: 5, propY: 6, propX: 7, propW: 8 });
|
||||||
|
|
||||||
|
let num = 1;
|
||||||
|
const prop = 'prop';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await object then { [`prop${num++}`]: { [`prop${num + 3}`]: propA }, [`prop${num++}`]: { [`prop${num + 5}`]: propB }, ...rest }}
|
||||||
|
<p>propA: {propA}</p>
|
||||||
|
<p>propB: {propB}</p>
|
||||||
|
<p>num: {num}</p>
|
||||||
|
<p>rest: {JSON.stringify(rest)}</p>
|
||||||
|
{/await}
|
||||||
|
|
||||||
|
{#await objectReject then value}
|
||||||
|
resolved
|
||||||
|
{:catch { [`${prop}Z`]: propZ, [`${prop}Y`]: propY, ...rest }}
|
||||||
|
<p>propZ: {propZ}</p>
|
||||||
|
<p>propY: {propY}</p>
|
||||||
|
<p>rest: {JSON.stringify(rest)}</p>
|
||||||
|
{/await}
|
||||||
|
|
@ -0,0 +1,63 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
thePromise: new Promise(_ => {})
|
||||||
|
},
|
||||||
|
|
||||||
|
html: `
|
||||||
|
loading...
|
||||||
|
`,
|
||||||
|
|
||||||
|
async test({ assert, component, target }) {
|
||||||
|
await (component.thePromise = Promise.resolve([10, 11, 12, 13, 14, 15]));
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<p>[1] 11</p>
|
||||||
|
<p>[3] 13</p>
|
||||||
|
<p>[4] 14</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await (component.thePromise = Promise.resolve({ 1: 21, 3: 23, 4: 24 }));
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<p>[1] 21</p>
|
||||||
|
<p>[3] 23</p>
|
||||||
|
<p>[4] 24</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await (component.thePromise = Promise.reject([30, 31, 32, 33, 34, 35]));
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<p>[0] 30</p>
|
||||||
|
<p>[2] 32</p>
|
||||||
|
<p>[5] 35</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await (component.thePromise = Promise.reject({ 0: 40, 2: 42, 5: 45 }));
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<p>[0] 40</p>
|
||||||
|
<p>[2] 42</p>
|
||||||
|
<p>[5] 45</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
export let thePromise;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await thePromise}
|
||||||
|
loading...
|
||||||
|
{:then { 1: a, 3: b, 4: c }}
|
||||||
|
<p>[1] {a}</p>
|
||||||
|
<p>[3] {b}</p>
|
||||||
|
<p>[4] {c}</p>
|
||||||
|
{:catch { 0: d, 2: e, 5: f }}
|
||||||
|
<p>[0] {d}</p>
|
||||||
|
<p>[2] {e}</p>
|
||||||
|
<p>[5] {f}</p>
|
||||||
|
{/await}
|
@ -0,0 +1,17 @@
|
|||||||
|
export default {
|
||||||
|
async test({ assert, target }) {
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<p>prop-1: 1</p>
|
||||||
|
<p>prop4: 4</p>
|
||||||
|
<p>rest: {"prop2":2,"prop-3":3}</p>
|
||||||
|
<p>prop-7: 7</p>
|
||||||
|
<p>prop6: 6</p>
|
||||||
|
<p>rest: {"prop-5":5,"prop8":8}</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
const object = Promise.resolve({ 'prop-1': 1, 'prop2': 2, 'prop-3': 3, 'prop4': 4 });
|
||||||
|
const objectReject = Promise.reject({ 'prop-5': 5, 'prop6': 6, 'prop-7': 7, 'prop8': 8 });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await object then { 'prop-1': prop1, 'prop4': fourthProp, ...rest }}
|
||||||
|
<p>prop-1: {prop1}</p>
|
||||||
|
<p>prop4: {fourthProp}</p>
|
||||||
|
<p>rest: {JSON.stringify(rest)}</p>
|
||||||
|
{/await}
|
||||||
|
|
||||||
|
{#await objectReject then value}
|
||||||
|
resolved
|
||||||
|
{:catch { 'prop-7': prop7, 'prop6': sixthProp, ...rest }}
|
||||||
|
<p>prop-7: {prop7}</p>
|
||||||
|
<p>prop6: {sixthProp}</p>
|
||||||
|
<p>rest: {JSON.stringify(rest)}</p>
|
||||||
|
{/await}
|
||||||
|
|
@ -0,0 +1,25 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
name: 'world'
|
||||||
|
},
|
||||||
|
|
||||||
|
ssrHtml: `
|
||||||
|
<editor contenteditable="true">world</editor>
|
||||||
|
<p>hello world</p>
|
||||||
|
`,
|
||||||
|
|
||||||
|
async test({ assert, component, target, window }) {
|
||||||
|
// JSDom doesn't support innerText yet, so the test is not ideal
|
||||||
|
// https://github.com/jsdom/jsdom/issues/1245
|
||||||
|
const el = target.querySelector('editor');
|
||||||
|
assert.equal(el.innerText, 'world');
|
||||||
|
|
||||||
|
const event = new window.Event('input');
|
||||||
|
el.innerText = 'everybody';
|
||||||
|
await el.dispatchEvent(event);
|
||||||
|
assert.equal(component.name, 'everybody');
|
||||||
|
|
||||||
|
component.name = 'goodbye';
|
||||||
|
assert.equal(el.innerText, 'goodbye');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
export let name;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<editor contenteditable="true" bind:innerText={name} />
|
||||||
|
<p>hello {name}</p>
|
@ -0,0 +1,40 @@
|
|||||||
|
export default {
|
||||||
|
async test({ assert, target, component, window }) {
|
||||||
|
const button = target.querySelector('button');
|
||||||
|
const clickEvent = new window.Event('click');
|
||||||
|
const changeEvent = new window.Event('change');
|
||||||
|
|
||||||
|
const [input1, input2] = target.querySelectorAll('input[type="checkbox"]');
|
||||||
|
function validate_inputs(v1, v2) {
|
||||||
|
assert.equal(input1.checked, v1);
|
||||||
|
assert.equal(input2.checked, v2);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(component.test, []);
|
||||||
|
validate_inputs(false, false);
|
||||||
|
|
||||||
|
component.test = ['a', 'b'];
|
||||||
|
validate_inputs(true, true);
|
||||||
|
|
||||||
|
input1.checked = false;
|
||||||
|
await input1.dispatchEvent(changeEvent);
|
||||||
|
assert.deepEqual(component.test, ['b']);
|
||||||
|
|
||||||
|
input2.checked = false;
|
||||||
|
await input2.dispatchEvent(changeEvent);
|
||||||
|
assert.deepEqual(component.test, []);
|
||||||
|
|
||||||
|
input1.checked = true;
|
||||||
|
input2.checked = true;
|
||||||
|
await input1.dispatchEvent(changeEvent);
|
||||||
|
await input2.dispatchEvent(changeEvent);
|
||||||
|
assert.deepEqual(component.test, ['b', 'a']);
|
||||||
|
|
||||||
|
await button.dispatchEvent(clickEvent);
|
||||||
|
assert.deepEqual(component.test, ['b', 'a']); // should it be ['a'] only? valid arguments for both outcomes
|
||||||
|
|
||||||
|
input1.checked = false;
|
||||||
|
await input1.dispatchEvent(changeEvent);
|
||||||
|
assert.deepEqual(component.test, []);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,14 @@
|
|||||||
|
<script>
|
||||||
|
export let test = [];
|
||||||
|
let hidden = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button on:click={() => hidden = !hidden}>
|
||||||
|
{hidden ? "show" : "hide"} b
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<label>a <input type="checkbox" bind:group={test} value="a" /></label>
|
||||||
|
{#if !hidden}
|
||||||
|
<label>b <input type="checkbox" bind:group={test} value="b" /></label>
|
||||||
|
{/if}
|
||||||
|
<label>c <input value="just here, so b is not the last input" /></label>
|
@ -0,0 +1,34 @@
|
|||||||
|
export default {
|
||||||
|
async test({ assert, target, component, window }) {
|
||||||
|
const button = target.querySelector('button');
|
||||||
|
const clickEvent = new window.Event('click');
|
||||||
|
const changeEvent = new window.Event('change');
|
||||||
|
|
||||||
|
const [input1, input2] = target.querySelectorAll('input[type="radio"]');
|
||||||
|
function validate_inputs(v1, v2) {
|
||||||
|
assert.equal(input1.checked, v1);
|
||||||
|
assert.equal(input2.checked, v2);
|
||||||
|
}
|
||||||
|
|
||||||
|
component.test = 'a';
|
||||||
|
validate_inputs(true, false);
|
||||||
|
|
||||||
|
component.test = 'b';
|
||||||
|
validate_inputs(false, true);
|
||||||
|
|
||||||
|
input1.checked = true;
|
||||||
|
await input1.dispatchEvent(changeEvent);
|
||||||
|
assert.deepEqual(component.test, 'a');
|
||||||
|
|
||||||
|
input2.checked = true;
|
||||||
|
await input2.dispatchEvent(changeEvent);
|
||||||
|
assert.deepEqual(component.test, 'b');
|
||||||
|
|
||||||
|
await button.dispatchEvent(clickEvent);
|
||||||
|
assert.deepEqual(component.test, 'b'); // should it be undefined? valid arguments for both outcomes
|
||||||
|
|
||||||
|
input1.checked = true;
|
||||||
|
await input1.dispatchEvent(changeEvent);
|
||||||
|
assert.deepEqual(component.test, 'a');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,14 @@
|
|||||||
|
<script>
|
||||||
|
export let test;
|
||||||
|
let hidden = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button on:click={() => hidden = !hidden}>
|
||||||
|
{hidden ? "show" : "hide"} b
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<label>a <input type="radio" bind:group={test} value="a" /></label>
|
||||||
|
{#if !hidden}
|
||||||
|
<label>b <input type="radio" bind:group={test} value="b" /></label>
|
||||||
|
{/if}
|
||||||
|
<label>c <input value="just here, so b is not the last input" /></label>
|
@ -0,0 +1,25 @@
|
|||||||
|
export default {
|
||||||
|
skip_if_ssr: true, // TODO would be nice to fix this in SSR as well
|
||||||
|
|
||||||
|
html: `
|
||||||
|
<p>selected: b</p>
|
||||||
|
|
||||||
|
<select>
|
||||||
|
<option value='a'>a</option>
|
||||||
|
<option value='b'>b</option>
|
||||||
|
<option value='c'>c</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<p>selected: b</p>
|
||||||
|
`,
|
||||||
|
|
||||||
|
test({ assert, component, target }) {
|
||||||
|
assert.equal(component.selected, 'b');
|
||||||
|
const select = target.querySelector('select');
|
||||||
|
const options = [...target.querySelectorAll('option')];
|
||||||
|
|
||||||
|
// option with selected attribute should be selected
|
||||||
|
assert.equal(select.value, 'b');
|
||||||
|
assert.ok(options[1].selected);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,13 @@
|
|||||||
|
<script>
|
||||||
|
export let selected;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>selected: {selected}</p>
|
||||||
|
|
||||||
|
<select bind:value={selected}>
|
||||||
|
<option>a</option>
|
||||||
|
<option selected>b</option>
|
||||||
|
<option>c</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<p>selected: {selected}</p>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue