Merge branch 'version-4' into ssr-2

pull/8516/head
Ben McCann 3 years ago
commit d99657466c

@ -22,14 +22,18 @@ jobs:
os: ubuntu-latest
- node-version: 18
os: ubuntu-latest
- node-version: 20
os: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm install
- run: npm run test:integration
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: node node_modules/puppeteer/install.js
- run: pnpm test:integration
env:
CI: true
Lint:
@ -37,10 +41,11 @@ jobs:
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3
with:
cache: npm
- run: 'npm i && npm run lint'
cache: pnpm
- run: 'pnpm i && pnpm lint'
Unit:
runs-on: ${{ matrix.os }}
timeout-minutes: 10
@ -57,12 +62,15 @@ jobs:
os: ubuntu-latest
- node-version: 18
os: ubuntu-latest
- node-version: 20
os: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3
with:
cache: npm
- run: npm install
cache: pnpm
- run: pnpm install
env:
SKIP_PREPARE: true
- run: npm run test:unit
- run: pnpm test:unit

@ -3,7 +3,8 @@
## Unreleased (4.0)
* **breaking** Minimum supported Node version is now Node 14
* **breaking** Minimum supported TypeScript version is now 5 (it will likely work with lower versions, but we make no guarantess about that)
* **breaking** Minimum supported webpack version is now webpack 5
* **breaking** Minimum supported TypeScript version is now TypeScript 5 (it will likely work with lower versions, but we make no guarantees about that)
* **breaking** Stricter types for `createEventDispatcher` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224))
* **breaking** Stricter types for `Action` and `ActionReturn` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224))
* **breaking** Stricter types for `onMount` - now throws a type error when returning a function asynchronously to catch potential mistakes around callback functions (see PR for migration instructions) ([#8136](https://github.com/sveltejs/svelte/pull/8136))

@ -72,9 +72,9 @@ Small pull requests are much easier to review and more likely to get merged.
### Installation
1. Ensure you have [npm](https://www.npmjs.com/get-npm) installed.
1. After cloning the repository, run `npm install` in the root of the repository.
1. To start a development server, run `npm run dev`.
1. Ensure you have [pnpm](https://pnpm.io/installation) installed.
1. After cloning the repository, run `pnpm install` in the root of the repository.
1. To compile in watch mode, run `pnpm dev`.
### Creating a branch
@ -94,8 +94,8 @@ Test samples are kept in `/test/xxx/samples` folder.
#### Running tests
1. To run test, run `npm run test`.
1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `npm run test -- -g transition`.
1. To run test, run `pnpm test`.
1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `pnpm test -- -g transition`.
##### Running solo test
@ -106,11 +106,11 @@ Test samples are kept in `/test/xxx/samples` folder.
##### Updating `.expected` files
1. Tests suites like `css`, `js`, `server-side-rendering` asserts that the generated output has to match the content in the `.expected` file. For example, in the `js` test suites, the generated js code is compared against the content in `expected.js`.
1. To update the content of the `.expected` file, run the test with `--update` flag. (`npm run test --update`)
1. To update the content of the `.expected` file, run the test with `--update` flag. (`pnpm test --update`)
### Style guide
[Eslint](https://eslint.org) will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `npm run lint`.
[Eslint](https://eslint.org) will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `pnpm lint`.
#### Code conventions
@ -122,8 +122,8 @@ Test samples are kept in `/test/xxx/samples` folder.
Please make sure the following is done when submitting a pull request:
1. Describe your **test plan** in your pull request description. Make sure to test your changes.
1. Make sure your code lints (`npm run lint`).
1. Make sure your tests pass (`npm run test`).
1. Make sure your code lints (`pnpm lint`).
1. Make sure your tests pass (`pnpm test`).
All pull requests should be opened against the `master` branch. Make sure the PR does only one thing, otherwise please split it.

@ -34,7 +34,7 @@ To install and work on Svelte locally:
```bash
git clone https://github.com/sveltejs/svelte.git
cd svelte
npm install
pnpm install
```
> Do not use Yarn to install the dependencies, as the specific package versions in `package-lock.json` are used to build and test Svelte.
@ -42,13 +42,13 @@ npm install
To build the compiler and all the other modules included in the package:
```bash
npm run build
pnpm build
```
To watch for changes and continually rebuild the package (this is useful if you're using [npm link](https://docs.npmjs.com/cli/link.html) to test out changes in a project locally):
To watch for changes and continually rebuild the package (this is useful if you're using [`pnpm link`](https://pnpm.io/cli/link) to test out changes in a project locally):
```bash
npm run dev
pnpm dev
```
The compiler is written in [TypeScript](https://www.typescriptlang.org/), but don't let that put you off — it's basically just JavaScript with type annotations. You'll pick it up in no time. If you're using an editor other than [Visual Studio Code](https://code.visualstudio.com/), you may need to install a plugin in order to get syntax highlighting and code hints, etc.
@ -57,13 +57,13 @@ The compiler is written in [TypeScript](https://www.typescriptlang.org/), but do
### Running Tests
```bash
npm run test
pnpm test
```
To filter tests, use `-g` (aka `--grep`). For example, to only run tests involving transitions:
```bash
npm run test -- -g transition
pnpm test -- -g transition
```

9435
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -121,6 +121,7 @@
"@rollup/plugin-virtual": "^3.0.1",
"@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0",
"@types/aria-query": "^5.0.1",
"@types/estree": "^1.0.0",
"@types/mocha": "^10.0.1",
"@types/node": "^14.14.31",
"@typescript-eslint/eslint-plugin": "^5.58.0",
@ -150,5 +151,9 @@
"tslib": "^2.5.0",
"typescript": "^5.0.4",
"util": "^0.12.5"
},
"packageManager": "pnpm@7.32.0",
"engines": {
"pnpm": ">=7.0.0"
}
}

File diff suppressed because it is too large Load Diff

@ -86,19 +86,6 @@ export default [
}
}
fs.writeFileSync(
`${dir}/package.json`,
JSON.stringify(
{
main: './index.js',
module: './index.mjs',
types: './index.d.ts',
},
null,
' '
)
);
fs.writeFileSync(
`${dir}/index.d.ts`,
`export * from '../types/runtime/${dir}/index.js';`

@ -38,6 +38,7 @@ import compiler_warnings from './compiler_warnings';
import compiler_errors from './compiler_errors';
import { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore';
import check_enable_sourcemap from './utils/check_enable_sourcemap';
import Tag from './nodes/shared/Tag';
interface ComponentOptions {
namespace?: string;
@ -110,6 +111,8 @@ export default class Component {
slots: Map<string, Slot> = new Map();
slot_outlets: Set<string> = new Set();
tags: Tag[] = [];
constructor(
ast: Ast,
source: string,
@ -761,6 +764,7 @@ export default class Component {
this.hoist_instance_declarations();
this.extract_reactive_declarations();
this.check_if_tags_content_dynamic();
}
post_template_walk() {
@ -1479,6 +1483,12 @@ export default class Component {
unsorted_reactive_declarations.forEach(add_declaration);
}
check_if_tags_content_dynamic() {
this.tags.forEach(tag => {
tag.check_if_content_dynamic();
});
}
warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') {
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) {

@ -59,6 +59,11 @@ export default class Attribute extends Node {
return expression;
});
}
if (this.dependencies.size > 0) {
parent.cannot_use_innerhtml();
parent.not_static_content();
}
}
get_dependencies() {

@ -27,6 +27,8 @@ export default class AwaitBlock extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression);

@ -33,6 +33,8 @@ export default class EachBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression);
this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring

@ -15,6 +15,7 @@ import { is_name_contenteditable, get_contenteditable_attr, has_contenteditable_
import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character, regex_box_size } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list';
import hash from '../utils/hash';
import Let from './Let';
import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
@ -503,6 +504,25 @@ export default class Element extends Node {
this.optimise();
component.apply_stylesheet(this);
if (this.parent) {
if (this.actions.length > 0 ||
this.animation ||
this.bindings.length > 0 ||
this.classes.length > 0 ||
this.intro || this.outro ||
this.handlers.length > 0 ||
this.styles.length > 0 ||
this.name === 'option' ||
this.is_dynamic_element ||
this.tag_expr.dynamic_dependencies().length ||
this.is_dynamic_element ||
component.compile_options.dev
) {
this.parent.cannot_use_innerhtml(); // need to use add_location
this.parent.not_static_content();
}
}
}
validate() {
@ -1262,6 +1282,20 @@ export default class Element extends Node {
}
});
}
get can_use_textcontent() {
return this.is_static_content && this.children.every(node => node.type === 'Text' || node.type === 'MustacheTag');
}
get can_optimise_to_html_string() {
const can_use_textcontent = this.can_use_textcontent;
const is_template_with_text_content = this.name === 'template' && can_use_textcontent;
return !is_template_with_text_content && !this.namespace && (this.can_use_innerhtml || can_use_textcontent) && this.children.length > 0;
}
hash() {
return `svelte-${hash(this.component.source.slice(this.start, this.end))}`;
}
}
const regex_starts_with_vowel = /^[aeiou]/;

@ -15,6 +15,8 @@ export default class Head extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.cannot_use_innerhtml();
if (info.attributes.length) {
component.error(info.attributes[0], compiler_errors.invalid_attribute_head);
return;

@ -18,6 +18,8 @@ export default class IfBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.scope = scope.child();
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, this.scope, info.expression);
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this));

@ -28,6 +28,9 @@ export default class InlineComponent extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
if (info.name !== 'svelte:component' && info.name !== 'svelte:self') {
const name = info.name.split('.')[0]; // accommodate namespaces
component.warn_if_undefined(name, info, scope);

@ -13,6 +13,8 @@ export default class KeyBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression);

@ -2,4 +2,9 @@ import Tag from './shared/Tag';
export default class RawMustacheTag extends Tag {
type: 'RawMustacheTag';
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
}
}

@ -60,5 +60,8 @@ export default class Slot extends Element {
}
component.slots.set(this.slot_name, this);
this.cannot_use_innerhtml();
this.not_static_content();
}
}

@ -18,6 +18,7 @@ const elements_without_text = new Set([
]);
const regex_ends_with_svg = /svg$/;
const regex_non_whitespace_characters = /[\S\u00A0]/;
export default class Text extends Node {
type: 'Text';
@ -63,4 +64,11 @@ export default class Text extends Node {
return false;
}
use_space(): boolean {
if (this.component.compile_options.preserveWhitespace) return false;
if (regex_non_whitespace_characters.test(this.data)) return false;
return !this.within_pre();
}
}

@ -197,6 +197,15 @@ export default class Expression {
});
}
dynamic_contextual_dependencies() {
return Array.from(this.contextual_dependencies).filter(name => {
return Array.from(this.template_scope.dependencies_for_name.get(name)).some(variable_name => {
const variable = this.component.var_lookup.get(variable_name);
return is_dynamic(variable);
});
});
}
// TODO move this into a render-dom wrapper?
manipulate(block?: Block, ctx?: string | void) {
// TODO ideally we wouldn't end up calling this method

@ -15,6 +15,7 @@ export default class Node {
next?: INode;
can_use_innerhtml: boolean;
is_static_content: boolean;
var: string;
attributes: Attribute[];
@ -33,6 +34,9 @@ export default class Node {
value: parent
}
});
this.can_use_innerhtml = true;
this.is_static_content = true;
}
cannot_use_innerhtml() {
@ -42,6 +46,11 @@ export default class Node {
}
}
not_static_content() {
this.is_static_content = false;
if (this.parent) this.parent.not_static_content();
}
find_nearest(selector: RegExp) {
if (selector.test(this.type)) return this;
if (this.parent) return this.parent.find_nearest(selector);

@ -8,6 +8,9 @@ export default class Tag extends Node {
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
component.tags.push(this);
this.cannot_use_innerhtml();
this.expression = new Expression(component, this, scope, info.expression);
this.should_cache = (
@ -15,4 +18,12 @@ export default class Tag extends Node {
(this.expression.dependencies.size && scope.names.has(info.expression.name))
);
}
is_dependencies_static() {
return this.expression.dynamic_contextual_dependencies().length === 0 && this.expression.dynamic_dependencies().length === 0;
}
check_if_content_dynamic() {
if (!this.is_dependencies_static()) {
this.not_static_content();
}
}
}

@ -143,9 +143,6 @@ export default class AwaitBlockWrapper extends Wrapper {
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
block.add_dependencies(this.node.expression.dependencies);
let is_dynamic = false;

@ -38,4 +38,10 @@ export default class CommentWrapper extends Wrapper {
parent_node
);
}
text() {
if (!this.renderer.options.preserveComments) return '';
return `<!--${this.node.data}-->`;
}
}

@ -80,8 +80,6 @@ export default class EachBlockWrapper extends Wrapper {
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
const { dependencies } = node.expression;
block.add_dependencies(dependencies);

@ -36,9 +36,6 @@ export class BaseAttributeWrapper {
this.parent = parent;
if (node.dependencies.size > 0) {
parent.cannot_use_innerhtml();
parent.not_static_content();
block.add_dependencies(node.dependencies);
}
}

@ -29,6 +29,7 @@ import is_dynamic from '../shared/is_dynamic';
import { is_name_contenteditable, has_contenteditable_attr } from '../../../utils/contenteditable';
import create_debugging_comment from '../shared/create_debugging_comment';
import { push_array } from '../../../../utils/push_array';
import CommentWrapper from '../Comment';
interface BindingGroup {
events: string[];
@ -288,24 +289,6 @@ export default class ElementWrapper extends Wrapper {
}
});
if (this.parent) {
if (node.actions.length > 0 ||
node.animation ||
node.bindings.length > 0 ||
node.classes.length > 0 ||
node.intro || node.outro ||
node.handlers.length > 0 ||
node.styles.length > 0 ||
this.node.name === 'option' ||
node.tag_expr.dynamic_dependencies().length ||
node.is_dynamic_element ||
renderer.options.dev
) {
this.parent.cannot_use_innerhtml(); // need to use add_location
this.parent.not_static_content();
}
}
this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling);
this.element_data_name = block.get_unique_name(`${this.var.name}_data`);
@ -445,6 +428,7 @@ export default class ElementWrapper extends Wrapper {
render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const { renderer } = this;
const hydratable = renderer.options.hydratable;
if (this.node.name === 'noscript') return;
@ -458,13 +442,15 @@ export default class ElementWrapper extends Wrapper {
b`${node} = ${render_statement};`
);
if (renderer.options.hydratable) {
const { can_use_textcontent, can_optimise_to_html_string } = this.node;
if (hydratable) {
if (parent_nodes) {
block.chunks.claim.push(b`
${node} = ${this.get_claim_statement(block, parent_nodes)};
${node} = ${this.get_claim_statement(block, parent_nodes, can_optimise_to_html_string)};
`);
if (!this.void && this.node.children.length > 0) {
if (!can_optimise_to_html_string && !this.void && this.node.children.length > 0) {
block.chunks.claim.push(b`
var ${nodes} = ${children};
`);
@ -502,15 +488,19 @@ export default class ElementWrapper extends Wrapper {
// insert static children with textContent or innerHTML
// skip textcontent for <template>. append nodes to TemplateElement.content instead
const can_use_textcontent = this.can_use_textcontent();
const is_template = this.node.name === 'template';
const is_template_with_text_content = is_template && can_use_textcontent;
if (!is_template_with_text_content && !this.node.namespace && (this.can_use_innerhtml || can_use_textcontent) && this.fragment.nodes.length > 0) {
if (can_optimise_to_html_string) {
if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') {
block.chunks.create.push(
b`${node}.textContent = ${string_literal((this.fragment.nodes[0] as TextWrapper).data)};`
);
let text: Node = string_literal((this.fragment.nodes[0] as TextWrapper).data);
if (hydratable) {
const variable = block.get_unique_name('textContent');
block.add_variable(variable, text);
text = variable;
}
block.chunks.create.push(b`${node}.textContent = ${text};`);
if (hydratable) {
block.chunks.claim.push(b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.textContent = ${text};`);
}
} else {
const state = {
quasi: {
@ -519,25 +509,33 @@ export default class ElementWrapper extends Wrapper {
}
};
const literal = {
let literal: Node = {
type: 'TemplateLiteral',
expressions: [],
quasis: []
};
const can_use_raw_text = !this.can_use_innerhtml && can_use_textcontent;
to_html((this.fragment.nodes as unknown as Array<ElementWrapper | TextWrapper>), block, literal, state, can_use_raw_text);
literal.quasis.push(state.quasi);
const can_use_raw_text = !this.node.can_use_innerhtml && can_use_textcontent;
to_html((this.fragment.nodes as unknown as Array<ElementWrapper | CommentWrapper | TextWrapper>), block, literal, state, can_use_raw_text);
literal.quasis.push(state.quasi as any);
block.chunks.create.push(
b`${node}.${this.can_use_innerhtml ? 'innerHTML' : 'textContent'} = ${literal};`
);
if (hydratable) {
const variable = block.get_unique_name('textContent');
block.add_variable(variable, literal);
literal = variable;
}
const property = this.node.can_use_innerhtml ? 'innerHTML' : 'textContent';
block.chunks.create.push(b`${node}.${property} = ${literal};`);
if (hydratable) {
block.chunks.claim.push(b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.${property} = ${literal};`);
}
}
} else {
this.fragment.nodes.forEach((child: Wrapper) => {
child.render(
block,
is_template ? x`${node}.content` : node,
this.node.name === 'template' ? x`${node}.content` : node,
nodes,
{ element_data_name: this.element_data_name }
);
@ -566,7 +564,7 @@ export default class ElementWrapper extends Wrapper {
this.add_styles(block);
this.add_manual_style_scoping(block);
if (nodes && this.renderer.options.hydratable && !this.void) {
if (nodes && hydratable && !this.void && !can_optimise_to_html_string) {
block.chunks.claim.push(
b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);`
);
@ -582,10 +580,6 @@ export default class ElementWrapper extends Wrapper {
block.renderer.dirty(this.node.tag_expr.dynamic_dependencies());
}
can_use_textcontent() {
return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag');
}
get_render_statement(block: Block) {
const { name, namespace, tag_expr } = this.node;
const reference = tag_expr.manipulate(block);
@ -606,7 +600,7 @@ export default class ElementWrapper extends Wrapper {
return x`@element(${reference})`;
}
get_claim_statement(block: Block, nodes: Identifier) {
get_claim_statement(block: Block, nodes: Identifier, to_optimise_hydration: boolean) {
const attributes = this.attributes
.filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name)
.map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`);
@ -624,6 +618,10 @@ export default class ElementWrapper extends Wrapper {
reference = x`(${this.node.tag_expr.manipulate(block)} || 'null').toUpperCase()`;
}
if (to_optimise_hydration) {
attributes.push(p`["data-svelte-h"]: true`);
}
if (this.node.namespace === namespaces.svg) {
return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`;
} else {
@ -1288,13 +1286,19 @@ export default class ElementWrapper extends Wrapper {
const regex_backticks = /`/g;
const regex_dollar_signs = /\$/g;
function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) {
function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) {
wrappers.forEach(wrapper => {
if (wrapper instanceof TextWrapper) {
if (wrapper instanceof CommentWrapper) {
state.quasi.value.raw += wrapper.text();
} else if (wrapper instanceof TextWrapper) {
// Don't add the <pre>/<textarea> newline logic here because pre/textarea.innerHTML
// would keep the leading newline, too, only someParent.innerHTML = '..<pre/textarea>..' won't
if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' ';
if (wrapper.use_space()) {
// use space instead of the text content
state.quasi.value.raw += ' ';
return;
}
const parent = wrapper.node.parent as Element;

@ -20,8 +20,6 @@ export default class HeadWrapper extends Wrapper {
) {
super(renderer, block, parent, node);
this.can_use_innerhtml = false;
this.fragment = new FragmentWrapper(
renderer,
block,

@ -105,9 +105,6 @@ export default class IfBlockWrapper extends Wrapper {
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
this.branches = [];
const blocks: Block[] = [];

@ -44,9 +44,6 @@ export default class InlineComponentWrapper extends Wrapper {
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
if (this.node.expression) {
block.add_dependencies(this.node.expression.dependencies);
}

@ -24,9 +24,6 @@ export default class KeyBlockWrapper extends Wrapper {
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
this.dependencies = node.expression.dynamic_dependencies();
if (this.dependencies.length) {

@ -20,8 +20,6 @@ export default class RawMustacheTagWrapper extends Tag {
node: MustacheTag | RawMustacheTag
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
}
render(block: Block, parent_node: Identifier, _parent_nodes: Identifier) {

@ -30,8 +30,6 @@ export default class SlotWrapper extends Wrapper {
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
if (this.node.children.length) {
this.fallback = block.child({

@ -5,11 +5,9 @@ import Wrapper from './shared/Wrapper';
import { x } from 'code-red';
import { Identifier } from 'estree';
const regex_non_whitespace_characters = /[\S\u00A0]/;
export default class TextWrapper extends Wrapper {
node: Text;
data: string;
_data: string;
skip: boolean;
var: Identifier;
@ -23,15 +21,22 @@ export default class TextWrapper extends Wrapper {
super(renderer, block, parent, node);
this.skip = this.node.should_skip();
this.data = data;
this._data = data;
this.var = (this.skip ? null : x`t`) as unknown as Identifier;
}
use_space() {
if (this.renderer.component.component_options.preserveWhitespace) return false;
if (regex_non_whitespace_characters.test(this.data)) return false;
return this.node.use_space();
}
return !this.node.within_pre();
set data(value: string) {
// when updating `this.data` during optimisation
// propagate the changes over to the underlying node
// so that the node.use_space reflects on the latest `data` value
this.node.data = this._data = value;
}
get data() {
return this._data;
}
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {

@ -12,18 +12,9 @@ export default class Tag extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
if (!this.is_dependencies_static()) {
this.not_static_content();
}
block.add_dependencies(node.expression.dependencies);
}
is_dependencies_static() {
return this.node.expression.contextual_dependencies.size === 0 && this.node.expression.dynamic_dependencies().length === 0;
}
rename_this_method(
block: Block,
update: ((value: Node) => (Node | Node[]))

@ -13,8 +13,6 @@ export default class Wrapper {
next: Wrapper | null;
var: Identifier;
can_use_innerhtml: boolean;
is_static_content: boolean;
constructor(
renderer: Renderer,
@ -35,22 +33,9 @@ export default class Wrapper {
}
});
this.can_use_innerhtml = !renderer.options.hydratable;
this.is_static_content = !renderer.options.hydratable;
block.wrappers.push(this);
}
cannot_use_innerhtml() {
this.can_use_innerhtml = false;
if (this.parent) this.parent.cannot_use_innerhtml();
}
not_static_content() {
this.is_static_content = false;
if (this.parent) this.parent.not_static_content();
}
get_or_create_anchor(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
// TODO use this in EachBlock and IfBlock — tricky because
// children need to be created first

@ -48,6 +48,7 @@ const handlers: Record<string, Handler> = {
export interface RenderOptions extends CompileOptions{
locate: (c: number) => { line: number; column: number };
head_id?: string;
has_added_svelte_hash?: boolean;
}
export default class Renderer {

@ -158,6 +158,13 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
}
});
if (options.hydratable) {
if (node.can_optimise_to_html_string && !options.has_added_svelte_hash) {
renderer.add_string(` data-svelte-h="${node.hash()}"`);
options = { ...options, has_added_svelte_hash: true };
}
}
renderer.add_string('>');
if (node_contents !== undefined) {

@ -5,7 +5,9 @@ import Element from '../../nodes/Element';
export default function(node: Text, renderer: Renderer, _options: RenderOptions) {
let text = node.data;
if (
if (node.use_space()) {
text = ' ';
} else if (
!node.parent ||
node.parent.type !== 'Element' ||
((node.parent as Element).name !== 'script' && (node.parent as Element).name !== 'style')

@ -39,6 +39,7 @@ export default function remove_whitespace_children(children: INode[], next?: INo
continue;
}
child.data = data;
nodes.unshift(child);
link(last_child, last_child = child);
} else {

@ -1,4 +1,5 @@
export function string_literal(data: string) {
import { Literal } from 'estree';
export function string_literal(data: string): Literal {
return {
type: 'Literal',
value: data

@ -362,6 +362,10 @@ export function xlink_attr(node, attribute, value) {
node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
}
export function get_svelte_dataset(node: HTMLElement) {
return node.dataset.svelteH;
}
export function get_binding_group_value(group, __value, checked) {
const value = new Set();
for (let i = 0; i < group.length; i += 1) {

@ -4,7 +4,7 @@ import glob from 'tiny-glob/sync';
import * as path from 'path';
import * as fs from 'fs';
import * as colors from 'kleur';
export const assert = (assert$1 as unknown) as typeof assert$1 & { htmlEqual: (actual, expected, message?) => void, htmlEqualWithComments: (actual, expected, message?) => void };
export const assert = (assert$1 as unknown) as typeof assert$1 & { htmlEqual: (actual: string, expected: string, message?: string) => void, htmlEqualWithOptions: (actual: string, expected: string, options: { preserveComments?: boolean, withoutNormalizeHtml?: boolean }, message?: string) => void };
// for coverage purposes, we need to test source files,
// but for sanity purposes, we need to test dist files
@ -140,11 +140,12 @@ function cleanChildren(node) {
}
}
export function normalizeHtml(window, html, preserveComments = false) {
export function normalizeHtml(window, html, { removeDataSvelte = false, preserveComments = false }: { removeDataSvelte?: boolean, preserveComments?: boolean }) {
try {
const node = window.document.createElement('div');
node.innerHTML = html
.replace(/(<!--.*?-->)/g, preserveComments ? '$1' : '')
.replace(/(data-svelte-h="[^"]+")/g, removeDataSvelte ? '' : '$1')
.replace(/>[ \t\n\r\f]+</g, '><')
.trim();
cleanChildren(node);
@ -154,22 +155,30 @@ export function normalizeHtml(window, html, preserveComments = false) {
}
}
export function setupHtmlEqual() {
export function normalizeNewline(html: string) {
return html.replace(/\r\n/g, '\n');
}
export function setupHtmlEqual(options: { removeDataSvelte?: boolean } = {}) {
const window = env();
// eslint-disable-next-line no-import-assign
assert.htmlEqual = (actual, expected, message) => {
assert.deepEqual(
normalizeHtml(window, actual),
normalizeHtml(window, expected),
normalizeHtml(window, actual, options),
normalizeHtml(window, expected, options),
message
);
};
// eslint-disable-next-line no-import-assign
assert.htmlEqualWithComments = (actual, expected, message) => {
assert.htmlEqualWithOptions = (actual: string, expected: string, { preserveComments, withoutNormalizeHtml }: { preserveComments?: boolean, withoutNormalizeHtml?: boolean }, message?: string) => {
assert.deepEqual(
normalizeHtml(window, actual, true),
normalizeHtml(window, expected, true),
withoutNormalizeHtml
? normalizeNewline(actual).replace(/(\sdata-svelte-h="[^"]+")/g, options.removeDataSvelte ? '' : '$1')
: normalizeHtml(window, actual, { ...options, preserveComments }),
withoutNormalizeHtml
? normalizeNewline(expected).replace(/(\sdata-svelte-h="[^"]+")/g, options.removeDataSvelte ? '' : '$1')
: normalizeHtml(window, expected, { ...options, preserveComments }),
message
);
};

@ -106,6 +106,13 @@ describe('hydration', () => {
}
}
if (config.snapshot) {
const snapshot_after = config.snapshot(target);
for (const s in snapshot_after) {
assert.equal(snapshot_after[s], snapshot[s], `Expected snapshot key "${s}" to have same value/reference`);
}
}
if (config.test) {
config.test(assert, target, snapshot, component, window);
} else {

@ -1 +1 @@
<h1>Hello world!</h1>
<h1 data-svelte-h="svelte-1vv3a6r">Hello world!</h1>

@ -1 +1 @@
<h1>Hello world!</h1>
<h1 data-svelte-h="svelte-1vv3a6r">Hello world!</h1>

@ -6,12 +6,5 @@ export default {
h1,
text: h1.childNodes[0]
};
},
test(assert, target, snapshot) {
const h1 = target.querySelector('h1');
assert.equal(h1, snapshot.h1);
assert.equal(h1.childNodes[0], snapshot.text);
}
};

@ -10,13 +10,8 @@ export default {
};
},
async test(assert, target, snapshot, component, window) {
async test(assert, target, _, component, window) {
const input = target.querySelector('input');
const p = target.querySelector('p');
assert.equal(input, snapshot.input);
assert.equal(p, snapshot.p);
input.value = 'everybody';
await input.dispatchEvent(new window.Event('input'));

@ -1 +1,3 @@
<div><!-- test1 --><!-- test2 --></div>
p
<div><!-- test1 --><!-- test2 --></div>

@ -1 +1,3 @@
<div><!-- test1 --></div>
p
<div><!-- test1 --><!-- test2 --></div>

@ -3,18 +3,8 @@ export default {
preserveComments:true
},
snapshot(target) {
const div = target.querySelector('div');
return {
div,
comment: div.childNodes[0]
div: target.querySelectorAll('div')[1]
};
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
assert.equal(div.childNodes[0], snapshot.comment);
assert.equal(div.childNodes[1].nodeType, 8);
}
};

@ -1 +1,3 @@
<div><!-- test1 --><!-- test2 --></div>
{"p"}
<div><!-- test1 --><!-- test2 --></div>

@ -0,0 +1,8 @@
<div data-svelte-h="xxx">hello</div>
<div data-svelte-h="xxx"><div>bye</div></div>
<div data-svelte-h="xxx">
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div data-svelte-h="xxx">hello</div>
<div data-svelte-h="xxx"><div data-svelte-h="yyy">bye</div></div>
<div data-svelte-h="xxx">
<div data-svelte-h="yyy">aaa</div>
<div data-svelte-h="zzz">bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
export default {
snapshot(target) {
return {
main: target.querySelector('main'),
p: target.querySelector('p')
};
}
};

@ -1,3 +1,3 @@
<div>
<p>nested</p>
<p data-svelte-h="svelte-1x3hbnh">nested</p>
</div>

@ -1,3 +1,3 @@
<div>
<p>nested</p>
<p data-svelte-h="svelte-1x3hbnh">nested</p>
</div>

@ -8,14 +8,5 @@ export default {
p,
text: p.childNodes[0]
};
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
const p = target.querySelector('p');
assert.equal(div, snapshot.div);
assert.equal(p, snapshot.p);
assert.equal(p.childNodes[0], snapshot.text);
}
};

@ -1 +1 @@
<p>nested</p>
<p data-svelte-h="svelte-1x3hbnh">nested</p>

@ -1 +1 @@
<p>nested</p>
<p data-svelte-h="svelte-1x3hbnh">nested</p>

@ -6,12 +6,5 @@ export default {
p,
text: p.childNodes[0]
};
},
test(assert, target, snapshot) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
assert.equal(p.childNodes[0], snapshot.text);
}
};

@ -10,12 +10,5 @@ export default {
h1,
text: h1.childNodes[0]
};
},
test(assert, target, snapshot) {
const h1 = target.querySelector('h1');
assert.equal(h1, snapshot.h1);
assert.equal(h1.childNodes[0], snapshot.text);
}
};

@ -9,13 +9,5 @@ export default {
nullText,
undefinedText
};
},
test(assert, target, snapshot) {
const nullText = target.querySelectorAll('p')[0].textContent;
const undefinedText = target.querySelectorAll('p')[1].textContent;
assert.equal(nullText, snapshot.nullText);
assert.equal(undefinedText, snapshot.undefinedText);
}
};

@ -10,12 +10,5 @@ export default {
h1,
text: h1.childNodes[0]
};
},
test(assert, target, snapshot) {
const h1 = target.querySelector('h1');
assert.equal(h1, snapshot.h1);
assert.equal(h1.childNodes[0], snapshot.text);
}
};

@ -15,17 +15,9 @@ export default {
return {
ul,
lis
lis0: lis[0],
lis1: lis[1],
lis2: lis[2]
};
},
test(assert, target, snapshot) {
const ul = target.querySelector('ul');
const lis = ul.querySelectorAll('li');
assert.equal(ul, snapshot.ul);
assert.equal(lis[0], snapshot.lis[0]);
assert.equal(lis[1], snapshot.lis[1]);
assert.equal(lis[2], snapshot.lis[2]);
}
};

@ -13,17 +13,9 @@ export default {
return {
ul,
lis
lis0: lis[0],
lis1: lis[1],
lis2: lis[2]
};
},
test(assert, target, snapshot) {
const ul = target.querySelector('ul');
const lis = ul.querySelectorAll('li');
assert.equal(ul, snapshot.ul);
assert.equal(lis[0], snapshot.lis[0]);
assert.equal(lis[1], snapshot.lis[1]);
assert.equal(lis[2], snapshot.lis[2]);
}
};

@ -9,11 +9,5 @@ export default {
return {
div
};
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
}
};

@ -9,11 +9,5 @@ export default {
return {
div
};
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
}
};

@ -9,11 +9,5 @@ export default {
return {
div
};
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
}
};

@ -5,11 +5,5 @@ export default {
return {
div
};
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
}
};

@ -7,13 +7,5 @@ export default {
span: p.querySelector('span'),
code: p.querySelector('code')
};
},
test(assert, target, snapshot) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
assert.equal(p.querySelector('span'), snapshot.span);
assert.equal(p.querySelector('code'), snapshot.code);
}
};

@ -1,3 +1,3 @@
<div>
<div data-svelte-h="svelte-1aqf5aj">
<p>nested</p>
</div>

@ -1,3 +1,3 @@
<div>
<div data-svelte-h="svelte-1aqf5aj">
<p>nested</p>
</div>

@ -6,12 +6,5 @@ export default {
div,
p: div.querySelector('p')
};
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
assert.equal(div.querySelector('p'), snapshot.p);
}
};

@ -7,10 +7,8 @@ export default {
};
},
test(assert, target, snapshot, component) {
test(assert, target, _, component) {
const h1 = target.querySelector('h1');
assert.equal(h1, snapshot.h1);
assert.equal(component.h1, h1);
}
};

@ -11,10 +11,8 @@ export default {
};
},
async test(assert, target, snapshot, component, window) {
async test(assert, target, _, component, window) {
const button = target.querySelector('button');
assert.equal(button, snapshot.button);
await button.dispatchEvent(new window.MouseEvent('click'));
assert.ok(component.clicked);

@ -7,13 +7,5 @@ export default {
text: p.childNodes[0],
span: p.querySelector('span')
};
},
test(assert, target, snapshot) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
assert.equal(p.childNodes[0], snapshot.text);
assert.equal(p.querySelector('span'), snapshot.span);
}
};

@ -13,14 +13,5 @@ export default {
p0: ps[0],
p1: ps[1]
};
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
const ps = target.querySelectorAll('p');
assert.equal(div, snapshot.div);
assert.equal(ps[0], snapshot.p0);
assert.equal(ps[1], snapshot.p1);
}
};

@ -9,11 +9,5 @@ export default {
return {
p
};
},
test(assert, target, snapshot) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
}
};

@ -12,10 +12,7 @@ export default {
};
},
test(assert, target, snapshot, component) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
test(assert, target, _, component) {
component.foo = false;
component.bar = true;

@ -9,11 +9,5 @@ export default {
return {
p
};
},
test(assert, target, snapshot) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
}
};

@ -14,14 +14,5 @@ export default {
p1: ps[1],
text1: ps[1].firstChild
};
},
test(assert, target, snapshot) {
const ps = target.querySelectorAll('p');
assert.equal(ps[0], snapshot.p0);
assert.equal(ps[0].firstChild, snapshot.text0);
assert.equal(ps[1], snapshot.p1);
assert.equal(ps[1].firstChild, snapshot.text1);
}
};

@ -9,17 +9,7 @@ function foo() {
}
const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => {
return `
<div class="class1 class2" style="color:red;">-</div>
<div${add_attribute("class", const1, 0)}>-</div>
<div${add_attribute("class", const1, 0)}>-</div>
<div class="${"class1 " + escape('class2', true)}">-</div>
<div class="${"class1 " + escape(const2, true)}">-</div>
<div class="${"class1 " + escape(const2, true)}"${add_attribute("style", foo(), 0)}>-</div>`;
return ` <div class="class1 class2" style="color:red;">-</div> <div${add_attribute("class", const1, 0)}>-</div> <div${add_attribute("class", const1, 0)}>-</div> <div class="${"class1 " + escape('class2', true)}">-</div> <div class="${"class1 " + escape(const2, true)}">-</div> <div class="${"class1 " + escape(const2, true)}"${add_attribute("style", foo(), 0)}>-</div>`;
});
export default Component;

@ -8,11 +8,8 @@ const Component = create_ssr_component(($$result, $$props, $$bindings, slots) =>
if ($$props.foo === void 0 && $$bindings.foo && foo !== void 0) $$bindings.foo(foo);
return `${each(things, thing => {
return `<span>${escape(thing.name)}</span>
${debug(null, 7, 2, { foo })}`;
})}
<p>foo: ${escape(foo)}</p>`;
return `<span>${escape(thing.name)}</span> ${debug(null, 7, 2, { foo })}`;
})} <p>foo: ${escape(foo)}</p>`;
});
export default Component;
export default Component;

@ -27,7 +27,6 @@ function get_each_context(ctx, list, i) {
function create_each_block(ctx) {
let div;
let strong;
let t0;
let t1;
let span;
let t2_value = /*comment*/ ctx[4].author + "";
@ -44,7 +43,7 @@ function create_each_block(ctx) {
c() {
div = element("div");
strong = element("strong");
t0 = text(/*i*/ ctx[6]);
strong.textContent = `${/*i*/ ctx[6]}`;
t1 = space();
span = element("span");
t2 = text(t2_value);
@ -60,7 +59,6 @@ function create_each_block(ctx) {
m(target, anchor) {
insert(target, div, anchor);
append(div, strong);
append(strong, t0);
append(div, t1);
append(div, span);
append(span, t2);

@ -2,9 +2,7 @@
import { create_ssr_component } from "svelte/internal";
const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => {
return `<div>content</div>
<!-- comment -->
<div>more content</div>`;
return `<div>content</div> <!-- comment --> <div>more content</div>`;
});
export default Component;
export default Component;

@ -45,7 +45,7 @@ describe('runtime', () => {
return module._compile(code, filename);
};
return setupHtmlEqual();
return setupHtmlEqual({ removeDataSvelte: true });
});
after(() => process.removeListener('unhandledRejection', unhandledRejection_handler));
@ -154,15 +154,23 @@ describe('runtime', () => {
window.SvelteComponent = SvelteComponent;
const target = window.document.querySelector('main');
let snapshot = undefined;
if (hydrate && from_ssr_html) {
// ssr into target
compileOptions.generate = 'ssr';
cleanRequireCache();
if (config.before_test) config.before_test();
const SsrSvelteComponent = require(`./samples/${dir}/main.svelte`).default;
const { html } = SsrSvelteComponent.render(config.props);
target.innerHTML = html;
if (config.snapshot) {
snapshot = config.snapshot(target);
}
delete compileOptions.generate;
if (config.after_test) config.after_test();
} else {
target.innerHTML = '';
}
@ -199,7 +207,9 @@ describe('runtime', () => {
}
if (config.html) {
assert.htmlEqual(target.innerHTML, config.html);
assert.htmlEqualWithOptions(target.innerHTML, config.html, {
withoutNormalizeHtml: config.withoutNormalizeHtml
});
}
if (config.test) {
@ -208,6 +218,7 @@ describe('runtime', () => {
component,
mod,
target,
snapshot,
window,
raf,
compileOptions

@ -1,6 +1,6 @@
// https://github.com/sveltejs/svelte/issues/7633
export default {
async test({ assert, target, component, window }) {
async test({ assert, target, component }) {
let inputs = target.querySelectorAll('input');
assert.equal(inputs[0].checked, true);

@ -1,6 +1,6 @@
// https://github.com/sveltejs/svelte/issues/7633
export default {
async test({ assert, target, component, window }) {
async test({ assert, target, component }) {
let inputs = target.querySelectorAll('input');
assert.equal(inputs[0].checked, true);

@ -4,7 +4,7 @@ export default {
<div>[N] B <button>Toggle</button></div>
<div>[N] C <button>Toggle</button></div>
`,
async test({ component, target, assert, window }) {
async test({ target, assert, window }) {
const [btn1, btn2, btn3] = target.querySelectorAll('button');
await btn1.dispatchEvent(new window.MouseEvent('click'));
await btn2.dispatchEvent(new window.MouseEvent('click'));

@ -5,7 +5,9 @@ export default {
<h1>tag is h1.</h1>
`,
props: {
logs
pushLogs(log) {
logs.push(log);
}
},
after_test() {
logs = [];

@ -1,12 +1,12 @@
<script>
export let logs = [];
export let pushLogs;
export let tag = "h1";
export let opt = "opt1";
function foo(node, {tag, opt}) {
logs.push(`create: ${tag},${opt}`);
pushLogs(`create: ${tag},${opt}`);
return {
update: ({tag, opt}) => logs.push(`update: ${tag},${opt}`),
destroy: () => logs.push('destroy'),
update: ({tag, opt}) => pushLogs(`update: ${tag},${opt}`),
destroy: () => pushLogs('destroy'),
};
}
</script>

@ -8,9 +8,9 @@ export default {
async test({ assert, target }) {
const firstSpanList = target.children[0];
assert.equal(firstSpanList.innerHTML, expected);
assert.htmlEqualWithOptions(firstSpanList.innerHTML, expected, { withoutNormalizeHtml: true });
const secondSpanList = target.children[1];
assert.equal(secondSpanList.innerHTML, expected);
assert.htmlEqualWithOptions(secondSpanList.innerHTML, expected, { withoutNormalizeHtml: true });
}
};

@ -1,19 +1,14 @@
export default {
test({ assert, target }) {
// Test for <pre> tag
const elementPre = target.querySelector('#pre');
// Test for non <pre> tag
const elementDiv = target.querySelector('#div');
// Test for <pre> tag in non <pre> tag
const elementDivWithPre = target.querySelector('#div-with-pre');
// Test for <pre> tag with leading newline
const elementPreWithLeadingNewline = target.querySelector('#pre-with-leading-newline');
const elementPreWithoutLeadingNewline = target.querySelector('#pre-without-leading-newline');
const elementPreWithMultipleLeadingNewline = target.querySelector('#pre-with-multiple-leading-newlines');
withoutNormalizeHtml: true,
html: get_html(false),
ssrHtml: get_html(true)
};
assert.equal(
elementPre.innerHTML,
` A
function get_html(ssr) {
// ssr rendered HTML has an extra newline prefixed within `<pre>` tag,
// if the <pre> tag starts with `\n`
// because when browser parses the SSR rendered HTML, it will ignore the 1st '\n' character
return `<pre id="pre"> A
B
<span>
C
@ -21,20 +16,12 @@ export default {
</span>
E
F
`
);
assert.equal(
elementDiv.innerHTML,
`A
</pre> <div id="div">A
B
<span>C
D</span>
E
F`
);
assert.equal(
elementDivWithPre.innerHTML,
`<pre> A
F</div> <div id="div-with-pre"><pre> A
B
<span>
C
@ -42,14 +29,9 @@ export default {
</span>
E
F
</pre>`
);
assert.equal(elementPreWithLeadingNewline.children[0].innerHTML, 'leading newline');
assert.equal(elementPreWithLeadingNewline.children[1].innerHTML, ' leading newline and spaces');
assert.equal(elementPreWithLeadingNewline.children[2].innerHTML, '\nleading newlines');
assert.equal(elementPreWithoutLeadingNewline.children[0].innerHTML, 'without spaces');
assert.equal(elementPreWithoutLeadingNewline.children[1].innerHTML, ' with spaces ');
assert.equal(elementPreWithoutLeadingNewline.children[2].innerHTML, ' \nnewline after leading space');
assert.equal(elementPreWithMultipleLeadingNewline.innerHTML, '\n\nmultiple leading newlines');
}
};
</pre></div> <div id="pre-with-leading-newline"><pre>leading newline</pre> <pre> leading newline and spaces</pre> <pre>${ssr ? '\n' : ''}
leading newlines</pre></div> <div id="pre-without-leading-newline"><pre>without spaces</pre> <pre> with spaces </pre> <pre>
newline after leading space</pre></div> <pre id="pre-with-multiple-leading-newlines">${ssr ? '\n' : ''}
multiple leading newlines</pre>`;
}

@ -1,18 +1,9 @@
export default {
compileOptions: {
preserveWhitespace: true
},
test({ assert, target }) {
// Test for <pre> tag
const elementPre = target.querySelector('#pre');
// Test for non <pre> tag
const elementDiv = target.querySelector('#div');
// Test for <pre> tag in non <pre> tag
const elementDivWithPre = target.querySelector('#div-with-pre');
},
assert.equal(
elementPre.innerHTML,
` A
html: `<pre id="pre"> A
B
<span>
C
@ -20,11 +11,9 @@ export default {
</span>
E
F
`
);
assert.equal(
elementDiv.innerHTML,
`
</pre>
<div id="div">
A
B
<span>
@ -33,11 +22,9 @@ export default {
</span>
E
F
`
);
assert.equal(
elementDivWithPre.innerHTML,
`
</div>
<div id="div-with-pre">
<pre> A
B
<span>
@ -47,7 +34,5 @@ export default {
E
F
</pre>
`
);
}
</div>`
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save