diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e0ce53864..8520c04310 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,72 +6,29 @@ on: permissions: contents: read # to fetch code (actions/checkout) jobs: - Setup: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: npm - - run: npm install - env: - SKIP_PREPARE: true - - run: npm run build - env: - PUBLISH: true - - name: Upload build assets - id: upload-artifact - uses: actions/upload-artifact@v3 - with: - name: build-assets - path: | - index.* - compiler.* - ssr.* - action/ - animate/ - easing/ - internal/ - motion/ - store/ - transition/ - types/ Tests: - needs: Setup runs-on: ${{ matrix.os }} timeout-minutes: 15 strategy: matrix: - node-version: [8, 10, 12, 14, 16, 18] - os: [ubuntu-latest, windows-latest, macOS-latest] + include: + - node-version: 14 + os: ubuntu-latest + - node-version: 14 + os: windows-latest + - node-version: 14 + os: macOS-latest + - node-version: 16 + os: ubuntu-latest + - node-version: 18 + os: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: npm - - name: Download build assets - uses: actions/download-artifact@v3 - id: download-artifact - with: - name: build-assets - - name: Get Node version ${{ runner.os }} - run: echo "NODE_VERSION=`node --version`" >> $GITHUB_ENV - if: runner.os != 'Windows' - - name: Get Node version ${{ runner.os }} - run: | - chcp 65001 - echo ("NODE_VERSION=$(node --version)") >> $env:GITHUB_ENV - if: runner.os == 'Windows' - - run: npm install --save-dev puppeteer@13 - if: ${{ runner.os == 'Linux' && (!startsWith(env.NODE_VERSION, 'v8.') && !startsWith(env.NODE_VERSION, 'v10.')) }} - run: npm install - env: - SKIP_PREPARE: true - run: npm run test:integration env: CI: true @@ -89,7 +46,17 @@ jobs: timeout-minutes: 10 strategy: matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] + include: + - node-version: 14 + os: ubuntu-latest + - node-version: 14 + os: windows-latest + - node-version: 14 + os: macOS-latest + - node-version: 16 + os: ubuntu-latest + - node-version: 18 + os: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc87cdb0e..fb855b2996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,46 @@ # Svelte changelog +## 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** 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)) +* Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391)) +* Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251)) +* Bind `null` option and input values consistently ([#8312](https://github.com/sveltejs/svelte/issues/8312)) + +## Unreleased (3.0) + +* Handle `width`/`height` attributes when spreading ([#6752](https://github.com/sveltejs/svelte/issues/6752)) +* Add support for resize observer bindings (`
`) ([#8022](https://github.com/sveltejs/svelte/pull/8022)) + +## 3.58.0 + +* Add `bind:innerText` for `contenteditable` elements ([#3311](https://github.com/sveltejs/svelte/issues/3311)) +* Add support for CSS `@container` queries ([#6969](https://github.com/sveltejs/svelte/issues/6969)) +* Respect `preserveComments` in DOM output ([#7182](https://github.com/sveltejs/svelte/pull/7182)) +* Allow use of `document` for `target` in typings ([#7554](https://github.com/sveltejs/svelte/pull/7554)) +* Add `a11y-interactive-supports-focus` warning ([#8392](https://github.com/sveltejs/svelte/pull/8392)) +* Fix equality check when updating dynamic text ([#5931](https://github.com/sveltejs/svelte/issues/5931)) +* Relax `a11y-no-noninteractive-element-to-interactive-role` warning ([#8402](https://github.com/sveltejs/svelte/pull/8402)) +* Properly handle microdata attributes ([#8413](https://github.com/sveltejs/svelte/issues/8413)) +* Prevent name collision when using computed destructuring variables ([#8417](https://github.com/sveltejs/svelte/issues/8417)) +* Fix escaping `', + ssrHtml: '' +}; diff --git a/test/runtime/samples/attribute-escape/main.svelte b/test/runtime/samples/attribute-escape/main.svelte new file mode 100644 index 0000000000..1b1fdf62a0 --- /dev/null +++ b/test/runtime/samples/attribute-escape/main.svelte @@ -0,0 +1 @@ +`} /> diff --git a/test/runtime/samples/attribute-microdata/_config.js b/test/runtime/samples/attribute-microdata/_config.js new file mode 100644 index 0000000000..8c6a7765ea --- /dev/null +++ b/test/runtime/samples/attribute-microdata/_config.js @@ -0,0 +1,25 @@ +// There is no relationship between the attribute and the dom node with regards to microdata attributes https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata +export default { + html: `
+ Game - REQUIRES + OS
+ + +
RATING: + 4.6 ( + 8864 ratings )
+
+
+ +
+ Price: $1.00 + +
+ ` +}; diff --git a/test/runtime/samples/attribute-microdata/main.svelte b/test/runtime/samples/attribute-microdata/main.svelte new file mode 100644 index 0000000000..f03a6943d9 --- /dev/null +++ b/test/runtime/samples/attribute-microdata/main.svelte @@ -0,0 +1,31 @@ + +
+ Game - REQUIRES + OS
+ + +
+ RATING: + 4.6 ( + 8864 ratings ) +
+
+
+ +
+ Price: $1.00 + +
diff --git a/test/runtime/samples/await-then-destruct-computed-props/_config.js b/test/runtime/samples/await-then-destruct-computed-props/_config.js new file mode 100644 index 0000000000..6d147535e3 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-computed-props/_config.js @@ -0,0 +1,32 @@ +export default { + async test({ assert, component, target }) { + await Promise.resolve(); + + assert.htmlEqual( + target.innerHTML, + ` +

propA: 3

+

propB: 7

+

num: 3

+

rest: {"prop3":{"prop9":9,"prop10":10}}

+

propZ: 5

+

propY: 6

+

rest: {"propX":7,"propW":8}

+ ` + ); + + await (component.object = Promise.resolve({ prop1: 'one', prop2: 'two', prop3: { prop7: 'seven' }, prop4: { prop10: 'ten' }})); + assert.htmlEqual( + target.innerHTML, + ` +

propA: seven

+

propB: ten

+

num: 5

+

rest: {"prop1":"one","prop2":"two"}

+

propZ: 5

+

propY: 6

+

rest: {"propX":7,"propW":8}

+ ` + ); + } +}; diff --git a/test/runtime/samples/await-then-destruct-computed-props/main.svelte b/test/runtime/samples/await-then-destruct-computed-props/main.svelte new file mode 100644 index 0000000000..71756d6cf3 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-computed-props/main.svelte @@ -0,0 +1,23 @@ + + +{#await object then { [`prop${num++}`]: { [`prop${num + 3}`]: propA }, [`prop${num++}`]: { [`prop${num + 5}`]: propB }, ...rest }} +

propA: {propA}

+

propB: {propB}

+

num: {num}

+

rest: {JSON.stringify(rest)}

+{/await} + +{#await objectReject then value} + resolved +{:catch { [`${prop}Z`]: propZ, [`${prop}Y`]: propY, ...rest }} +

propZ: {propZ}

+

propY: {propY}

+

rest: {JSON.stringify(rest)}

+{/await} + diff --git a/test/runtime/samples/binding-contenteditable-innertext/_config.js b/test/runtime/samples/binding-contenteditable-innertext/_config.js new file mode 100644 index 0000000000..36e762ac24 --- /dev/null +++ b/test/runtime/samples/binding-contenteditable-innertext/_config.js @@ -0,0 +1,25 @@ +export default { + props: { + name: 'world' + }, + + ssrHtml: ` + world +

hello world

+ `, + + 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'); + } +}; diff --git a/test/runtime/samples/binding-contenteditable-innertext/main.svelte b/test/runtime/samples/binding-contenteditable-innertext/main.svelte new file mode 100644 index 0000000000..d65350d1a8 --- /dev/null +++ b/test/runtime/samples/binding-contenteditable-innertext/main.svelte @@ -0,0 +1,6 @@ + + + +

hello {name}

diff --git a/test/runtime/samples/binding-select-null-placeholder/_config.js b/test/runtime/samples/binding-select-null-placeholder/_config.js new file mode 100644 index 0000000000..b453e6869a --- /dev/null +++ b/test/runtime/samples/binding-select-null-placeholder/_config.js @@ -0,0 +1,28 @@ +const items = [ { id: 'a' }, { id: 'b' } ]; + +export default { + props: { + foo: null, + items + }, + + test({ assert, component, target }) { + const select = target.querySelector( 'select' ); + const options = target.querySelectorAll( 'option' ); + + assert.equal( options[0].selected, true ); + assert.equal( options[0].disabled, true ); + assert.equal( options[1].selected, false ); + assert.equal( options[1].disabled, false ); + + // placeholder option value must be blank string for native required field validation + assert.equal( options[0].value, '' ); + assert.equal( select.checkValidity(), false ); + + component.foo = items[0]; + + assert.equal( options[0].selected, false ); + assert.equal( options[1].selected, true ); + assert.equal( select.checkValidity(), true ); + } +}; diff --git a/test/runtime/samples/binding-select-null-placeholder/main.svelte b/test/runtime/samples/binding-select-null-placeholder/main.svelte new file mode 100644 index 0000000000..65cab99495 --- /dev/null +++ b/test/runtime/samples/binding-select-null-placeholder/main.svelte @@ -0,0 +1,11 @@ + + + diff --git a/test/runtime/samples/component-event-handler-contenteditable/_config.js b/test/runtime/samples/component-event-handler-contenteditable/_config.js deleted file mode 100644 index 1628e22d01..0000000000 --- a/test/runtime/samples/component-event-handler-contenteditable/_config.js +++ /dev/null @@ -1,15 +0,0 @@ -export default { - html: ` -
- `, - - async test({ assert, target, window }) { - const div = target.querySelector('div'); - const text = window.document.createTextNode('a'); - div.insertBefore(text, null); - const event = new window.InputEvent('input'); - await div.dispatchEvent(event); - - assert.equal(div.textContent, 'a'); - } -}; diff --git a/test/runtime/samples/const-tag-await-then-destructuring-computed-in-computed/_config.js b/test/runtime/samples/const-tag-await-then-destructuring-computed-in-computed/_config.js new file mode 100644 index 0000000000..9ec4a3e3e6 --- /dev/null +++ b/test/runtime/samples/const-tag-await-then-destructuring-computed-in-computed/_config.js @@ -0,0 +1,20 @@ +export default { + html: ` +

4, 12, 60

+ `, + + async test({ component, target, assert }) { + component.permutation = [2, 3, 1]; + await (component.promise1 = Promise.resolve({length: 1, width: 2, height: 3})); + try { + await (component.promise2 = Promise.reject({length: 97, width: 98, height: 99})); + } catch (e) { + // nothing + } + + assert.htmlEqual(target.innerHTML, ` +

2, 11, 2

+

9506, 28811, 98

+ `); + } +}; diff --git a/test/runtime/samples/const-tag-await-then-destructuring-computed-in-computed/main.svelte b/test/runtime/samples/const-tag-await-then-destructuring-computed-in-computed/main.svelte new file mode 100644 index 0000000000..285fc44348 --- /dev/null +++ b/test/runtime/samples/const-tag-await-then-destructuring-computed-in-computed/main.svelte @@ -0,0 +1,27 @@ + + +{#await promise1 then { length, width, height }} + {@const { [0]: a, [1]: b, [2]: c } = permutation} + {@const { [`${a}-Dimensions`]: { [c - 1]: first }, [`${b}-Dimensions`]: { [b - 1]: second }, [`${c}-Dimensions`]: { [a - 1]: third } } = calculate(length, width, height) } +

{first}, {second}, {third}

+{/await} + +{#await promise2 catch { [`leng${th}`]: l, [`wid${th}`]: w, height: h }} + {@const [a, b, c] = permutation} + {@const { [`${a}-Dimensions`]: { [c - 1]: first }, [`${b}-Dimensions`]: { [b - 1]: second }, [`${c}-Dimensions`]: { [a - 1]: third } } = calculate(l, w, h) } +

{first}, {second}, {third}

+{/await} diff --git a/test/runtime/samples/const-tag-await-then-destructuring-computed-props/_config.js b/test/runtime/samples/const-tag-await-then-destructuring-computed-props/_config.js new file mode 100644 index 0000000000..9ec4a3e3e6 --- /dev/null +++ b/test/runtime/samples/const-tag-await-then-destructuring-computed-props/_config.js @@ -0,0 +1,20 @@ +export default { + html: ` +

4, 12, 60

+ `, + + async test({ component, target, assert }) { + component.permutation = [2, 3, 1]; + await (component.promise1 = Promise.resolve({length: 1, width: 2, height: 3})); + try { + await (component.promise2 = Promise.reject({length: 97, width: 98, height: 99})); + } catch (e) { + // nothing + } + + assert.htmlEqual(target.innerHTML, ` +

2, 11, 2

+

9506, 28811, 98

+ `); + } +}; diff --git a/test/runtime/samples/const-tag-await-then-destructuring-computed-props/main.svelte b/test/runtime/samples/const-tag-await-then-destructuring-computed-props/main.svelte new file mode 100644 index 0000000000..fbc54e4d0b --- /dev/null +++ b/test/runtime/samples/const-tag-await-then-destructuring-computed-props/main.svelte @@ -0,0 +1,25 @@ + + +{#await promise1 then { length, width, height }} + {@const [a, b, c] = permutation} + {@const { [`${a}-Dimensions`]: { [c - 1]: first }, [`${b}-Dimensions`]: { [b - 1]: second }, [`${c}-Dimensions`]: { [a - 1]: third } } = calculate(length, width, height) } +

{first}, {second}, {third}

+{/await} + +{#await promise2 catch { length, width, height }} + {@const [a, b, c] = permutation} + {@const { [`${a}-Dimensions`]: { [c - 1]: first }, [`${b}-Dimensions`]: { [b - 1]: second }, [`${c}-Dimensions`]: { [a - 1]: third } } = calculate(length, width, height) } +

{first}, {second}, {third}

+{/await} diff --git a/test/runtime/samples/const-tag-each-destructure-computed-in-computed/_config.js b/test/runtime/samples/const-tag-each-destructure-computed-in-computed/_config.js new file mode 100644 index 0000000000..c299f19da6 --- /dev/null +++ b/test/runtime/samples/const-tag-each-destructure-computed-in-computed/_config.js @@ -0,0 +1,15 @@ +export default { + html: ` + + + + `, + + async test({ component, target, assert }) { + component.boxes = [{ length: 10, width: 20, height: 30 }]; + + assert.htmlEqual(target.innerHTML, + '' + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-destructure-computed-in-computed/main.svelte b/test/runtime/samples/const-tag-each-destructure-computed-in-computed/main.svelte new file mode 100644 index 0000000000..26dc5557ed --- /dev/null +++ b/test/runtime/samples/const-tag-each-destructure-computed-in-computed/main.svelte @@ -0,0 +1,44 @@ + + +{#each boxes as { [`leng${th}`]: length, [`wid${th}`]: width, height }} + {@const { + [`two${dimension}`]: areas, + [`three${dimension}`]: { + volume + } + } = calculate(length, width, height)} + {@const { + i = 1, + [`bottom${area}`]: bottom, + [`side${area}${i++}`]: sideone, + [`side${area}${i++}`]: sidetwo + } = areas} + +{/each} diff --git a/test/runtime/samples/const-tag-each-destructure-computed-props/_config.js b/test/runtime/samples/const-tag-each-destructure-computed-props/_config.js new file mode 100644 index 0000000000..c299f19da6 --- /dev/null +++ b/test/runtime/samples/const-tag-each-destructure-computed-props/_config.js @@ -0,0 +1,15 @@ +export default { + html: ` + + + + `, + + async test({ component, target, assert }) { + component.boxes = [{ length: 10, width: 20, height: 30 }]; + + assert.htmlEqual(target.innerHTML, + '' + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-destructure-computed-props/main.svelte b/test/runtime/samples/const-tag-each-destructure-computed-props/main.svelte new file mode 100644 index 0000000000..c8c7e1977a --- /dev/null +++ b/test/runtime/samples/const-tag-each-destructure-computed-props/main.svelte @@ -0,0 +1,42 @@ + + +{#each boxes as { length, width, height }} + {@const { + [`two${dimension}`]: { + i = 1, + [`bottom${area}`]: bottom, + [`side${area}${i++}`]: sideone, + [`side${area}${i++}`]: sidetwo + }, + [`three${dimension}`]: { + volume + } + } = calculate(length, width, height)} + +{/each} diff --git a/test/runtime/samples/each-block-destructured-array-computed-props/_config.js b/test/runtime/samples/each-block-destructured-array-computed-props/_config.js new file mode 100644 index 0000000000..e400e2f280 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-array-computed-props/_config.js @@ -0,0 +1,24 @@ +export default { + props: { + array: [ + [1, 2, 3, 4, 5], + [6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16, 17, 18, 19, 20, 21, 22] + ] + }, + + html: ` +

First: 1, Half: 3, Last: 5, Length: 5

+

First: 6, Half: 7, Last: 8, Length: 3

+

First: 9, Half: 11, Last: 12, Length: 4

+

First: 13, Half: 18, Last: 22, Length: 10

+ `, + + test({ assert, component, target }) { + component.array = [[23, 24, 25, 26, 27, 28, 29]]; + assert.htmlEqual( target.innerHTML, ` +

First: 23, Half: 26, Last: 29, Length: 7

+ `); + } +}; diff --git a/test/runtime/samples/each-block-destructured-array-computed-props/main.svelte b/test/runtime/samples/each-block-destructured-array-computed-props/main.svelte new file mode 100644 index 0000000000..00a9a99a89 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-array-computed-props/main.svelte @@ -0,0 +1,7 @@ + + +{#each array as { 0: first, length, [length - 1]: last, [Math.floor(length / 2)]: half }} +

First: {first}, Half: {half}, Last: {last}, Length: {length}

+{/each} diff --git a/test/runtime/samples/each-block-destructured-object-computed-props/_config.js b/test/runtime/samples/each-block-destructured-object-computed-props/_config.js new file mode 100644 index 0000000000..7e98fab913 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-computed-props/_config.js @@ -0,0 +1,26 @@ +export default { + props: { + firstString: 'cats', + secondString: 'dogs', + objectsArray: [ + { dogs: 'woof', cats: 'meow', stac: 'stack', DOGS: 'WOOF' }, + { dogs: 'A German sheppard', cats: 'A tailless cat', stac: 'A jenga tower', DOGS: 'A GERMAN SHEPPARD' }, + { dogs: 'dogs', cats: 'cats', stac: 'stac', DOGS: 'DOGS' } + ] + }, + + html: ` +

cats: meow

+

dogs: woof

+

stac: stack

+

DOGS: WOOF

+

cats: A tailless cat

+

dogs: A German sheppard

+

stac: A jenga tower

+

DOGS: A GERMAN SHEPPARD

+

cats: cats

+

dogs: dogs

+

stac: stac

+

DOGS: DOGS

+ ` +}; diff --git a/test/runtime/samples/each-block-destructured-object-computed-props/main.svelte b/test/runtime/samples/each-block-destructured-object-computed-props/main.svelte new file mode 100644 index 0000000000..9c2ae29617 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-computed-props/main.svelte @@ -0,0 +1,12 @@ + + +{#each objectsArray as { [firstString]: firstProp, [secondString]: secondProp, [firstString.split('').reverse().join('')]: reverseFirst, [secondString.toUpperCase()]: upperSecond } } +

{firstString}: {firstProp}

+

{secondString}: {secondProp}

+

{firstString.split('').reverse().join('')}: {reverseFirst}

+

{secondString.toUpperCase()}: {upperSecond}

+{/each} diff --git a/test/runtime/samples/html-entities-inside-component-slot/Component.svelte b/test/runtime/samples/html-entities-inside-component-slot/Component.svelte new file mode 100644 index 0000000000..8063609738 --- /dev/null +++ b/test/runtime/samples/html-entities-inside-component-slot/Component.svelte @@ -0,0 +1,3 @@ +
+ +
diff --git a/test/runtime/samples/html-entities-inside-component-slot/_config.js b/test/runtime/samples/html-entities-inside-component-slot/_config.js new file mode 100644 index 0000000000..33777a4919 --- /dev/null +++ b/test/runtime/samples/html-entities-inside-component-slot/_config.js @@ -0,0 +1,11 @@ +export default { + html: ` +
 
+ +
+   +
+ +
 
+ ` +}; diff --git a/test/runtime/samples/html-entities-inside-component-slot/main.svelte b/test/runtime/samples/html-entities-inside-component-slot/main.svelte new file mode 100644 index 0000000000..8baecfedcc --- /dev/null +++ b/test/runtime/samples/html-entities-inside-component-slot/main.svelte @@ -0,0 +1,13 @@ + + +  + + +   + + + + {@html " "} + diff --git a/test/runtime/samples/reactive-statement-indirect/_config.js b/test/runtime/samples/reactive-statement-indirect/_config.js new file mode 100644 index 0000000000..9265ded073 --- /dev/null +++ b/test/runtime/samples/reactive-statement-indirect/_config.js @@ -0,0 +1,14 @@ +export default { + html: ` +

2

+ + `, + async test({ assert, target }) { + await target.querySelector('button').dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` +

4

+ + `); + } +}; diff --git a/test/runtime/samples/reactive-statement-indirect/main.svelte b/test/runtime/samples/reactive-statement-indirect/main.svelte new file mode 100644 index 0000000000..ab75c19127 --- /dev/null +++ b/test/runtime/samples/reactive-statement-indirect/main.svelte @@ -0,0 +1,11 @@ + + +

{indirect_double}

+ diff --git a/test/runtime/samples/reactive-values-text-node/_config.js b/test/runtime/samples/reactive-values-text-node/_config.js new file mode 100644 index 0000000000..fb859285dd --- /dev/null +++ b/test/runtime/samples/reactive-values-text-node/_config.js @@ -0,0 +1,9 @@ +export default { + html:'
same text
', + async test({ assert, target }) { + await new Promise(f => setTimeout(f, 10)); + assert.htmlEqual(target.innerHTML, ` +
same text text
+ `); + } +}; diff --git a/test/runtime/samples/reactive-values-text-node/main.svelte b/test/runtime/samples/reactive-values-text-node/main.svelte new file mode 100644 index 0000000000..0982622b1a --- /dev/null +++ b/test/runtime/samples/reactive-values-text-node/main.svelte @@ -0,0 +1,8 @@ + + +
{text} text
diff --git a/test/runtime/samples/spread-width-height-attributes/_config.js b/test/runtime/samples/spread-width-height-attributes/_config.js new file mode 100644 index 0000000000..cf2dc7efde --- /dev/null +++ b/test/runtime/samples/spread-width-height-attributes/_config.js @@ -0,0 +1,4 @@ +export default { + // https://github.com/sveltejs/svelte/issues/6752 + html: '' +}; diff --git a/test/runtime/samples/spread-width-height-attributes/main.svelte b/test/runtime/samples/spread-width-height-attributes/main.svelte new file mode 100644 index 0000000000..b91b008457 --- /dev/null +++ b/test/runtime/samples/spread-width-height-attributes/main.svelte @@ -0,0 +1 @@ + diff --git a/test/runtime/samples/transition-js-deferred-option-direction/_config.js b/test/runtime/samples/transition-js-deferred-option-direction/_config.js index 2bf448d9c7..71adc5539f 100644 --- a/test/runtime/samples/transition-js-deferred-option-direction/_config.js +++ b/test/runtime/samples/transition-js-deferred-option-direction/_config.js @@ -4,16 +4,19 @@ export default { const div_in = target.querySelector('#in'); const div_out = target.querySelector('#out'); - const div_both = target.querySelector('#both'); + const div_bothin = target.querySelector('#both-in'); + const div_bothout = target.querySelector('#both-out'); - assert.equal(div_in.initial, 'in'); - assert.equal(div_out.initial, 'out'); - assert.equal(div_both.initial, 'both'); + assert.equal(div_in.directions, 'in,in'); + assert.equal(div_out.directions, 'out'); + assert.equal(div_bothin.directions, 'both'); + assert.equal(div_bothout.directions, 'both'); return Promise.resolve().then(() => { - assert.equal(div_in.later, 'in'); - assert.equal(div_out.later, 'out'); - assert.equal(div_both.later, 'both'); + assert.equal(div_in.directions, 'in,in'); + assert.equal(div_out.directions, 'out,out'); + assert.equal(div_bothin.directions, 'both,in'); + assert.equal(div_bothout.directions, 'both,out'); }); } }; diff --git a/test/runtime/samples/transition-js-deferred-option-direction/main.svelte b/test/runtime/samples/transition-js-deferred-option-direction/main.svelte index 9ac816ce75..76bca2b5e2 100644 --- a/test/runtime/samples/transition-js-deferred-option-direction/main.svelte +++ b/test/runtime/samples/transition-js-deferred-option-direction/main.svelte @@ -2,10 +2,10 @@ export let visible; function foo(node, _params, options) { - node.initial = options.direction; + node.directions = options.direction; return (opts) => { - node.later = opts.direction; + node.directions += "," + opts.direction; return { duration: 10 @@ -15,10 +15,11 @@ {#if visible} -
-
+
+
{/if} {#if !visible} -
+
+
{/if} diff --git a/test/types/actions.ts b/test/types/actions.ts new file mode 100644 index 0000000000..2a604151a8 --- /dev/null +++ b/test/types/actions.ts @@ -0,0 +1,153 @@ +import type { Action, ActionReturn } from '$runtime/action'; + +// ---------------- Action + +const href: Action = (node) => { + node.href = ''; + // @ts-expect-error + node.href = 1; +}; +href; + +const required: Action = (node, param) => { + node; + param; +}; +required(null as any, true); +// @ts-expect-error (only in strict mode) boolean missing +required(null as any); +// @ts-expect-error no boolean +required(null as any, 'string'); + +const required1: Action = (node, param) => { + node; + param; + return { + update: (p) => p === true, + destroy: () => {} + }; +}; +required1; + +const required2: Action = (node) => { + node; +}; +required2; + +const required3: Action = (node, param) => { + node; + param; + return { + // @ts-expect-error comparison always resolves to false + update: (p) => p === 'd', + destroy: () => {} + }; +}; +required3; + +const optional: Action = (node, param?) => { + node; + param; +}; +optional(null as any, true); +optional(null as any); +// @ts-expect-error no boolean +optional(null as any, 'string'); + +const optional1: Action = (node, param?) => { + node; + param; + return { + update: (p) => p === true, + destroy: () => {} + }; +}; +optional1; + +const optional2: Action = (node) => { + node; +}; +optional2; + +const optional3: Action = (node, param) => { + node; + param; +}; +optional3; + +const optional4: Action = (node, param?) => { + node; + param; + return { + // @ts-expect-error comparison always resolves to false + update: (p) => p === 'd', + destroy: () => {} + }; +}; +optional4; + +const no: Action = (node) => { + node; +}; +// @ts-expect-error second param +no(null as any, true); +no(null as any); +// @ts-expect-error second param +no(null as any, 'string'); + +const no1: Action = (node) => { + node; + return { + destroy: () => {} + }; +}; +no1; + +// @ts-expect-error param given +const no2: Action = (node, param?) => {}; +no2; + +// @ts-expect-error param given +const no3: Action = (node, param) => {}; +no3; + +// @ts-expect-error update method given +const no4: Action = (node) => { + return { + update: () => {}, + destroy: () => {} + }; +}; +no4; + +// ---------------- ActionReturn + +const requiredReturn: ActionReturn = { + update: (p) => p.toString() +}; +requiredReturn; + +const optionalReturn: ActionReturn = { + update: (p) => { + p === true; + // @ts-expect-error could be undefined + p.toString(); + } +}; +optionalReturn; + +const invalidProperty: ActionReturn = { + // @ts-expect-error invalid property + invalid: () => {} +}; +invalidProperty; + +type Attributes = ActionReturn['$$_attributes']; +const attributes: Attributes = { a: 'a' }; +attributes; +// @ts-expect-error wrong type +const invalidAttributes1: Attributes = { a: 1 }; +invalidAttributes1; +// @ts-expect-error missing prop +const invalidAttributes2: Attributes = {}; +invalidAttributes2; diff --git a/test/types/create-event-dispatcher.ts b/test/types/create-event-dispatcher.ts new file mode 100644 index 0000000000..d9fc6c65bd --- /dev/null +++ b/test/types/create-event-dispatcher.ts @@ -0,0 +1,43 @@ +import { createEventDispatcher } from '$runtime/internal/lifecycle'; + +const dispatch = createEventDispatcher<{ + loaded: never + change: string + valid: boolean + optional: number | null +}>(); + +// @ts-expect-error: dispatch invalid event +dispatch('some-event'); + +dispatch('loaded'); +dispatch('loaded', null); +dispatch('loaded', undefined); +dispatch('loaded', undefined, { cancelable: true }); +// @ts-expect-error: no detail accepted +dispatch('loaded', 123); + +// @ts-expect-error: detail not provided +dispatch('change'); +dispatch('change', 'string'); +dispatch('change', 'string', { cancelable: true }); +// @ts-expect-error: wrong type of detail +dispatch('change', 123); +// @ts-expect-error: wrong type of detail +dispatch('change', undefined); + +dispatch('valid', true); +dispatch('valid', true, { cancelable: true }); +// @ts-expect-error: wrong type of detail +dispatch('valid', 'string'); + +dispatch('optional'); +dispatch('optional', 123); +dispatch('optional', 123, { cancelable: true }); +dispatch('optional', null); +dispatch('optional', undefined); +dispatch('optional', undefined, { cancelable: true }); +// @ts-expect-error: wrong type of optional detail +dispatch('optional', 'string'); +// @ts-expect-error: wrong type of option +dispatch('optional', undefined, { cancelabled: true }); diff --git a/test/types/tsconfig.json b/test/types/tsconfig.json new file mode 100644 index 0000000000..108ed2a2b2 --- /dev/null +++ b/test/types/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "../..", + "baseUrl": "../../", + "paths": { + "$runtime/*": ["src/runtime/*"] + }, + // enable strictest options + "allowUnreachableCode": false, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "strict": true, + }, + "include": ["."] +} \ No newline at end of file diff --git a/test/utils/index.ts b/test/utils/index.ts index a8aed9387a..37ba63b1c7 100644 --- a/test/utils/index.ts +++ b/test/utils/index.ts @@ -1,5 +1,6 @@ import * as assert from 'assert'; import { trim_start, trim_end } from '../../src/compiler/utils/trim'; +import { split_css_unit } from '../../src/runtime/internal/utils'; describe('utils', () => { describe('trim', () => { @@ -13,4 +14,17 @@ describe('utils', () => { assert.equal(value, ' \r\n\t svelte content'); }); }); + + describe('split_css_unit', () => { + it('should use px as default', () => { + assert.deepEqual(split_css_unit(10), [10, 'px']); + assert.deepEqual(split_css_unit('10'), [10, 'px']); + }); + + it('should split the css notation into value and unit', () => { + assert.deepEqual(split_css_unit('-50%'), [-50, '%']); + assert.deepEqual(split_css_unit('0.1rem'), [0.1, 'rem']); + assert.deepEqual(split_css_unit('.1rem'), [0.1, 'rem']); + }); + }); }); diff --git a/test/validator/samples/a11y-click-events-have-key-events/input.svelte b/test/validator/samples/a11y-click-events-have-key-events/input.svelte index 8737f04ec5..3fb1ded53d 100644 --- a/test/validator/samples/a11y-click-events-have-key-events/input.svelte +++ b/test/validator/samples/a11y-click-events-have-key-events/input.svelte @@ -9,13 +9,20 @@ +
+
+
+
+
+
+