Merge branch 'master' into word-wrap

pull/9046/head
Ben McCann 3 years ago
commit fb5ac5c3d3

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: head duplication when binding is present

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: take custom attribute name into account when reflecting property

@ -0,0 +1,264 @@
---
title: Unlocking view transitions in SvelteKit 1.24
description: Streamlined page transitions with onNavigate
author: Geoff Rich
authorURL: https://geoffrich.net
---
The [view transitions API](https://developer.chrome.com/docs/web-platform/view-transitions/) has been sweeping the web development world lately, and for good reason. It streamlines the process of animating between two page states, which is especially useful for page transitions.
However, until now, you couldnt easily use this API in a SvelteKit app, since it was difficult to slot into the right place in the navigation lifecycle. SvelteKit 1.24 brought a new [`onNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-onnavigate) lifecycle hook to make view transitions integration much easier lets dive in.
## How view transitions work
You can trigger a view transition by calling `document.startViewTransition` and passing a callback that updates the DOM somehow. For our purposes today, SvelteKit will update the DOM as the user navigates. Once the callback finishes, the browser will transition to the new page state — by default, it does a crossfade between the old and the new states.
```js
// @errors: 2339
const domUpdate = async () => {};
// ---cut---
document.startViewTransition(async () => {
await domUpdate(); // mock function for demonstration purposes
});
```
Behind the scenes, the browser does something really clever. When the transition starts, it captures the current state of the page and takes a screenshot. It then holds that screenshot in place while the DOM is updating. Once the DOM has finished updating, it captures the new state, and animates between the two states.
While its only implemented in Chrome (and other Chromium-based browsers) for now, [WebKit is also in favor](https://github.com/WebKit/standards-positions/issues/48#issuecomment-1679760489) of it. Even if youre on an unsupported browser, its a perfect candidate for progressive enhancement since we can always fall back to a non-animated navigation.
Its important to note that view transitions is a browser API, not a SvelteKit one. `onNavigate` is the only SvelteKit-specific API well use today. Everything else can be used wherever you write for the web! For more on the view transitions API, I highly recommend the [Chrome explainer](https://developer.chrome.com/docs/web-platform/view-transitions/) by Jake Archibald.
## How `onNavigate` works
Before learning how to write view transitions, let's highlight the function that makes it all possible: [`onNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-onnavigate).
Until recently, SvelteKit had two navigation lifecycle functions: [`beforeNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-beforenavigate), which fires before a navigation starts, and [`afterNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-afternavigate), which fires after the page has been updated following a navigation. SvelteKit 1.24 introduces a third: `onNavigate`, which will fire on every navigation, immediately before the new page is rendered. Importantly, it will run _after_ any data loading for the page has completed since starting a view transition prevents any interaction with the page, we want to start it as late as possible.
You can also return a promise from `onNavigate`, which will suspend the navigation until it resolves. This will let us wait to complete the navigation until the view transition has started.
```js
// @errors: 2304 7006
function delayNavigation() {
return new Promise((res) => setTimeout(res, 100));
}
onNavigate(async (navigation) => {
// do some work immediately before the navigation completes
// optionally return a promise to delay navigation until it resolves
return delayNavigation();
});
```
With that out of the way, let's see how you can use view transitions in your SvelteKit app.
## Getting started with view transitions
The best way to see view transitions in action is to try it yourself. You can spin up the SvelteKit demo app by running `npm create svelte@latest` in your local terminal, or in your browser on [StackBlitz](https://sveltekit.new). Make sure to use a browser that supports the view transitions API. Once you have the app running, add the following to the script block in `src/routes/+layout.svelte`.
```js
// @errors: 2305 7006 2339 2810
import { onNavigate } from '$app/navigation';
onNavigate((navigation) => {
if (!document.startViewTransition) return;
return new Promise((resolve) => {
document.startViewTransition(async () => {
resolve();
await navigation.complete;
});
});
});
```
With that, every navigation that occurs will trigger a view transition. You can already see this in action by default, the browser will crossfade between the old and new pages.
<video src="https://sveltejs.github.io/assets/video/vt-demo-1.mp4" controls muted playsinline></video>
<details>
<summary>How the code works</summary>
This code may look a bit intimidating if you're curious, I can break it down line-by-line, but for now its enough to know that adding it will allow you to interact with the view transitions API during navigation.
As mentioned above, the `onNavigate` callback will run immediately before the new page is rendered after a navigation. Inside the callback, we check if `document.startViewTransition` exists. If it doesnt (i.e. the browser doesnt support it), we exit early.
We then return a promise to delay completing the navigation until the view transition has started. We use a [promise constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) so that we can control when the promise resolves.
```js
// @errors: 1108
return new Promise((resolve) => {
document.startViewTransition(async () => {
resolve();
await navigation.complete;
});
});
```
Inside the promise constructor, we start the view transition. Inside the view transition callback we resolve the promise we just returned, which indicates to SvelteKit that it should finish the navigation. Its important that the navigation waits to finish until _after_ we start the view transition the browser needs to snapshot the old state so it can transition to the new state.
Finally, inside the view transition callback we wait for SvelteKit to finish the navigation by awaiting `navigation.complete`. Once `navigation.complete` resolves, the new page has been loaded into the DOM and the browser can animate between the two states.
Its a bit of a mouthful, but by not abstracting it we allow you to interact with the view transition directly and make any customizations you require.
</details>
## Customizing the transition with CSS
We can also customize this page transition using CSS animation. In the style block of your `+layout.svelte`, add the following CSS rules.
```css
@keyframes fade-in {
from {
opacity: 0;
}
}
@keyframes fade-out {
to {
opacity: 0;
}
}
@keyframes slide-from-right {
from {
transform: translateX(30px);
}
}
@keyframes slide-to-left {
to {
transform: translateX(-30px);
}
}
:root::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out, 300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
:root::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms cubic-bezier(0.4, 0, 0.2, 1) both
slide-from-right;
}
```
Now when you navigate between pages, the old page will fade out and slide to the left, and the new page will fade in and slide from the right. These particular animation styles come from Jake Archibalds excellent [Chrome Developers article on view transitions](https://developer.chrome.com/docs/web-platform/view-transitions/), which is well worth a read if you want to understand everything you can do with this API.
Note that we have to add `:root` before the `::view-transition` pseudoelements these elements are only on the root of the document, so we dont want Svelte to [scope them](/docs/svelte-components#style) to the component.
You might have noticed that the entire page slides in and out, even though the header is the same on both the old and new page. To make for a smoother transition, we can give the header a unique `view-transition-name` so that it is animated separately from the rest of the page. In `src/routes/Header.svelte`, find the `header` CSS selector in the style block and add a view transition name.
```css
header {
display: flex;
justify-content: space-between;
view-transition-name: header;
}
```
Now, the header will not transition in and out on navigation, but the rest of the page will.
<video src="https://sveltejs.github.io/assets/video/vt-demo-2.mp4" controls muted playsinline></video>
<details>
<summary>Fixing the types</summary>
Since `startViewTransition` is not supported by all browsers, your IDE may not know that it exists. To make the errors go away and get the correct typings, add the following to your `app.d.ts`:
```ts
declare global {
// preserve any customizations you have here
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
// add these lines
interface ViewTransition {
updateCallbackDone: Promise<void>;
ready: Promise<void>;
finished: Promise<void>;
skipTransition: () => void;
}
interface Document {
startViewTransition(updateCallback: () => Promise<void>): ViewTransition;
}
}
export {};
```
</details>
## Transitioning individual elements
We just saw how giving an element a `view-transition-name` separates it out from the rest of the page's animation. Setting a `view-transition-name` also instructs the browser to smoothly animate it to its new position after the transition completes. The `view-transition-name` acts as a unique identifier so the browser can identify matching elements from the old and new states.
Lets see what that looks like our demo apps navigation has a small triangle indicating the active page. Right now, it abruptly appears in the new position after we navigate. Lets give it a `view-transition-name` so the browser animates it to its new position instead.
Inside `src/routes/Header.svelte`, find the CSS rule creating the active page indicator and give it a `view-transition-name`:
```css
li[aria-current='page']::before {
/* other existing rules */
view-transition-name: active-page;
}
```
By adding that single line, the indicator will now smoothly slide to its new position instead of jumping.
<video src="https://sveltejs.github.io/assets/video/vt-demo-3.mp4" controls muted playsinline></video>
(It might be easy to miss the difference look at the small moving triangle indicator at the top of the screen!)
## Reduced motion
Its important to respect our users [motion preferences](https://web.dev/prefers-reduced-motion/) while implementing animation on the web. Just because you can implement an extreme page transition doesnt mean you should. To disable all page transitions for users who prefer reduced motion, you can add the following to the global `styles.css`:
```css
@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
```
While this may be the safest option, reduced motion does not necessarily mean no animation. Instead, you could consider your view transitions on a case-by-case basis. For instance, maybe we disable the sliding animation, but leave the default crossfade (which doesnt involve motion). You can do so by wrapping the `::view-transition` rules you want to disable in a `prefers-reduced-motion: no-preference` media-query:
```css
@media (prefers-reduced-motion: no-preference) {
:root::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out, 300ms cubic-bezier(0.4, 0, 0.2, 1) both
slide-to-left;
}
:root::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms cubic-bezier(
0.4,
0,
0.2,
1
) both slide-from-right;
}
}
```
## Whats next?
As you can see, SvelteKit doesnt abstract a whole lot about _how_ view transitions work youre interacting directly with the browsers built-in `document.startViewTransition` and `::view-transition` APIs, rather than framework abstractions like those found in Nuxt and Astro. Were eager to see how people end up using view transitions in SvelteKit apps, and whether it makes sense to add higher level abstractions of our own in future.
## Resources
You can find the demo code from this post [on GitHub](https://github.com/geoffrich/sveltekit-onnavigate-demo) and the live version [deployed to Vercel](https://sveltekit-onnavigate-demo.vercel.app/). Here are some other view transitions resources you may find helpful:
- [MDN view transitions docs](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
- [Chrome view transitions API explainer](https://developer.chrome.com/docs/web-platform/view-transitions/)
- [Rich Harris demoing view transitions with onNavigate](https://www.youtube.com/shorts/weOCWOD2UIo)
- [My Svelte Summit video showing how to use view transitions for FLIP animations](https://youtu.be/K95TQ-Yh7Cw)
- [Fruit list demo](https://sveltekit-shared-element-transitions-codelab.vercel.app/fruits) ([source](https://github.com/geoffrich/sveltekit-view-transitions))
- [Svelte Summit video list demo](https://http-203-svelte.vercel.app/) (based on a [Jake Archibald demo](https://http203-playlist.netlify.app/)) ([source](https://github.com/geoffrich/http-203-svelte))

@ -0,0 +1,97 @@
---
title: "What's new in Svelte: September 2023"
description: "New parameters in SvelteKit's redirect and an onNavigate lifecycle function come to life"
author: Dani Sandoval
authorURL: https://dreamindani.com
---
Happy September y'all! With all the [sneak peeks at what's coming soon in Svelte 5](https://twitter.com/Rich_Harris/status/1688581184018583558), we thought it'd be best to look back at the last month to see what's shipped and what the community is building with Svelte.
Before we jump in, a warm welcome to the new Svelte Ambassadors: [@cainux](https://github.com/cainux) and [@grischaerbe](https://github.com/grischaerbe)! Welcome to the crew ⛴️
## What's new in Svelte & Language Tools
- `svelteHTML` has moved from language-tools into Svelte core so that `svelte/element` types will now load correctly (**4.2.0** in Svelte, **107.10.0** in Language Tools)
## What's new in SvelteKit
- `URL` is now accepted in the `redirect` function (**1.23.0**, [Docs](https://kit.svelte.dev/docs/modules#sveltejs-kit-redirect), [#10570](https://github.com/sveltejs/kit/pull/10570))
- Mistyped route filenames will now throw a warning (**1.23.0**, [#10558](https://github.com/sveltejs/kit/pull/10558))
- The new `onNavigate` lifecycle function enables view transitions - Check out the [blog post](https://svelte.dev/blog/view-transitions) for more info (**1.24.0**, [Docs](https://kit.svelte.dev/docs/modules#app-navigation-onnavigate), [#9605](https://github.com/sveltejs/kit/pull/9605))
But that's just the new features! For all the patches and performance updates from this month, check out the [SvelteKit CHANGELOG](https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md). You can also find adapter-specific CHANGELOGs in each of [the `adapter` directories](https://github.com/sveltejs/kit/tree/master/packages).
---
## Community Showcase
**Apps & Sites built with Svelte**
- [Planet Of The Bugs](https://planetofthebugs.xyz/) allows developers to practice and hone their skill-sets by exposing them to an endless supply of unique, curated issues and bugs from popular open-source projects on Github
- [Minesweeper](https://github.com/ProductionPanic/minesweeper/tree/main) is an Android game built with SvelteKit, Capacitor, TailwindCSS and DaisyUI (check it out on the [Google Play Store](https://play.google.com/store/apps/details?id=com.production.panic.minesweeper&pli=1))
- [Pendor](https://www.pendor.ai/) is an AI component generator for Svelte
- [Avatars Pro](https://senja.io/testimonial-widgets/avatars-pro) is a social proof widget made for the web
- [Pomodoro Focus](https://github.com/con-dog/pomodoro-focus) is a pomodoro timer browser extension
- [memegen](https://github.com/bhupeshpr25/memegen) is a Firefox web extension that allows users to generate memes using various templates
- [Resgen](https://resgen.app/) is a Chrome extension that tailors resumes based on job descriptions and your experiences
- [Icono Search](https://www.icono-search.com) is an AI-powered video search engine
- [digital-paper](https://github.com/danferns/digital-paper) is a writing app with no backspace or undo
- [Ubuntu 22.04 in Svelte](https://github.com/manhhungpc/ubuntu2204-svelte) aims to replicate the Ubuntu 22.04 desktop experience on the web
- [My Queue](https://www.myqueue.so/) creates a playlist of written articles by turning them into audio stories
**Learning Resources**
_Featuring Svelte Contributors and Ambassadors_
- [Svelte Society - London August 2023](https://www.youtube.com/watch?v=90Psdk5rAnU)
- [Building a Blog using SvelteKit and Nostr as a CMS (Part 1](https://kevinak.se/blog/building-a-blog-using-sveltekit-and-nostr-as-a-cms-part-1-1690807337563)) by Kev
- [Mastering SvelteKit with Geoff Rich | JS Drops](https://www.youtube.com/watch?v=MaF8kRbHbi0) by This Dot Media
- [Using GitHub Contributions To Flex On The Normies](https://youtu.be/f9fd1L1FEts?si=3hbihW-X5-GKSJxN), [Learn Svelte By Making A Matching Game](https://www.youtube.com/watch?v=w2q9caYXgkg) and [Who Needs API Permission When You Can Use Web Scraping](https://www.youtube.com/watch?v=T-lBPpeokfY) by Joy of Code
- [The missing guide to understanding adapter-static in SvelteKit](https://khromov.se/the-missing-guide-to-understanding-adapter-static-in-sveltekit/) by Stanislav Khromov
- This Week in Svelte:
- [2023 July 28](https://www.youtube.com/watch?v=mvTEQ_C0qRQ) - Screen reader market share, Svelte to plain JS, Web Components
- [2023 Aug 4](https://www.youtube.com/watch?v=Ye8cCJyPZjg) - Svelte 4.1.2, SvelteKit 1.22.4, ES Modules, Types in markup
- [2023 August 11](https://www.youtube.com/watch?v=A8XUaiCVkCI) - Svelte 4.2.0, SvelteKit 1.22.5, How to create Toggle Switches
- [2023 August 18](https://www.youtube.com/watch?v=nJ5Wf3uL7dM) - SvelteKit 1.22.6, accessible form error summaries
- [2023 August 25](https://www.youtube.com/watch?v=JoPzvlBKXXE) - SvelteKit 1.23.0, Bun and SvelteKit, Enhanced search
- Svienna (Svelte Society Vienna) Sessions
- [Ermin Celikovic - You might not need a slider library](https://www.youtube.com/watch?v=dSUmtijkFOc)
- [Lukas Stracke - How to use sentry.io in your SvelteKit App](https://www.youtube.com/watch?v=u41-MtPGH04)
- [Jean-Yves Couet - SvelteKit & Remult... fullstack apps in minutes!](https://www.youtube.com/watch?v=N8d290fTzq8)
- Sirens Sessions
- [Prismic Slice Machines & SvelteKit](https://www.youtube.com/watch?v=19Meb-yMsAg) with Sam Littlefair
- [Medusa and SvelteKit E-Commerce Stack](https://www.youtube.com/watch?v=rVVHxows9dY) with Lacey Pevey
- [Design Systems: Lessons Learned](https://www.youtube.com/watch?v=YHZaiIGSqsE) with Eric Liu
_To Watch_
- [Image optimization in SvelteKit with vite-imagetools](https://www.youtube.com/watch?v=285vSLe9LQ8) by hartenfellerdev
- [Building a Todo App with Rust and SvelteKit: Complete Tutorial](https://www.youtube.com/watch?v=w7is2bCTUg0) and [Stripe Payment In SvelteKit With Dynamic Pricing](https://www.youtube.com/watch?v=o8gvCLgz1vs) by SvelteRust
- [Leaflet maps in SvelteKit like it's 2023 (HowTo)](https://www.youtube.com/watch?v=JFctWXEzFZw)
ShipBit
_To Read_
- [Internationalization in SvelteKit (Series)](https://blog.aakashgoplani.in/series/i18n-in-sveltekit) by Aakash Goplani
- [The easiest Chatbot you will ever build](https://simon-prammer.vercel.app/blog/post/sveltekit-langchain) and [Intro to LangSmith🦜🛠](https://simon-prammer.vercel.app/blog/post/langsmith) by Simon Prammer
- [SvelteKit: How to make code-based router, instead of file-based router [August 2023]](https://dev.to/maxcore/sveltekit-how-to-make-code-based-router-instead-of-file-based-router-august-2023-5f9) by Max Core
- [SvelteKit Hydration Gotcha](https://www.captaincodeman.com/sveltekit-hydration-gotcha) by Captain Codeman
- [Automatically generate sitemap.xml in SvelteKit](https://alex-schnabl.medium.com/automatically-generate-sitemap-xml-in-sveltekit-910bd09d17e7) by Alex Schnabl
- [Discovering Svelte: Things I Learned While Using Svelte](https://www.tronic247.com/discovering-svelte-things-i-learned-while-using-svelte/) by Posandu Mapa
- [Typed fetch with Sveltekit and Hono using RPC](https://dev.to/subhendupsingh/typed-fetch-with-sveltekit-and-hono-using-rpc-2clf) by Subhendu Pratap Singh
- [Svelte Context Module Scripts Explained](https://raqueebuddinaziz.com/blog/svelte-context-module-scripts-explained) by raqueebuddin aziz
- [Building with GPT4 and Svelte](https://kvak.io/meoweler) by levmiseri
- [Type-safe User Authentication in SvelteKit with Lucia, Planetscale, and Upstash Redis](https://upstash.com/blog/lucia-sveltekit) by Chris Jayden
- [Document Svelte Projects with HTML and JSDoc Comments](https://blog.robino.dev/posts/doc-comments-svelte) by Ross Robino
**Libraries, Tools & Components**
- [Carta](https://github.com/BearToCode/carta-md) is a lightweight, fast and extensible Svelte Markdown editor and viewer, based on Marked
- [Threlte](https://threlte.xyz/), the 3D framework built from Svelte and Three.js has released version 6
- [vite-plugin-web-extension](https://vite-plugin-web-extension.aklinker1.io/guide/frontend-frameworks.html#svelte-integration) works great with Svelte to make building browser extensions easier
- [Salvia-kit Svelte Dashboards](https://github.com/salvia-kit/svelte-dashboards) contains 10 free dashboard templates for SvelteKit
- [drab](https://github.com/rossrobino/drab) is an Unstyled Svelte component library
- [svelte-img-previewer](https://www.npmjs.com/package/svelte-img-previewer?activeTab=readme) is a tool for displaying images from input file types in Svelte
- [sveltekit-search-params](https://github.com/paoloricciuti/sveltekit-search-params) describes itself as the fastest way to read AND write from query search params in SvelteKit
That's it for this month! Feel free to let us know if we missed anything on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.gg/svelte).
Until next time 👋

@ -21,7 +21,7 @@ SvelteKit will handle calling [the Svelte compiler](https://www.npmjs.com/packag
### Alternatives to SvelteKit
If you don't want to use SvelteKit for some reason, you can also use Svelte with Vite (but without SvelteKit) by running `npm init vite` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS and CSS files inside the `dist` directory. In most cases, you will probably need to [choose a routing library](/faq#is-there-a-router) as well.
If you don't want to use SvelteKit for some reason, you can also use Svelte with Vite (but without SvelteKit) by running `npm create vite@latest` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS and CSS files inside the `dist` directory. In most cases, you will probably need to [choose a routing library](/faq#is-there-a-router) as well.
Alternatively, there are [plugins for all the major web bundlers](https://sveltesociety.dev/tools#bundling) to handle Svelte compilation — which will output `.js` and `.css` that you can insert into your HTML — but most others won't handle SSR.

@ -106,11 +106,13 @@ An element or component can have multiple spread attributes, interspersed with r
## Text expressions
A JavaScript expression can be included as text by surrounding it with curly braces.
```svelte
{expression}
```
Text can also contain JavaScript expressions:
Curly braces can be included in a Svelte template by using their [HTML entity](https://developer.mozilla.org/docs/Glossary/Entity) strings: `&lbrace;`, `&lcub;`, or `&#123;` for `{` and `&rbrace;`, `&rcub;`, or `&#125;` for `}`.
> If you're using a regular expression (`RegExp`) [literal notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#literal_notation_and_constructor), you'll need to wrap it in parentheses.

@ -131,6 +131,17 @@ If you need hash-based routing on the client side, check out [svelte-spa-router]
You can see a [community-maintained list of routers on sveltesociety.dev](https://sveltesociety.dev/components#routers).
## Can I tell Svelte not to remove my unused styles?
No. Svelte removes the styles from the component and warns you about them in order to prevent issues that would otherwise arise.
Svelte's component style scoping works by generating a class unique to the given component, adding it to the relevant elements in the component that are under Svelte's control, and then adding it to each of the selectors in that component's styles. When the compiler can't see what elements a style selector applies to, there would be two bad options for keeping it:
- If it keeps the selector and adds the scoping class to it, the selector will likely not match the expected elements in the component, and they definitely won't if they were created by a child component or `{@html ...}`.
- If it keeps the selector without adding the scoping class to it, the given style will become a global style, affecting your entire page.
If you need to style something that Svelte can't identify at compile time, you will need to explicitly opt into global styles by using `:global(...)`. But also keep in mind that you can wrap `:global(...)` around only part of a selector. `.foo :global(.bar) { ... }` will style any `.bar` elements that appear within the component's `.foo` elements. As long as there's some parent element in the current component to start from, partially global selectors like this will almost always be able to get you what you want.
## Is Svelte v2 still available?
New features aren't being added to it, and bugs will probably only be fixed if they are extremely nasty or present some sort of security vulnerability.

@ -68,6 +68,10 @@
</div>
<style>
:root {
scrollbar-gutter: stable;
}
.easing-vis {
display: flex;
max-height: 95%;

@ -74,18 +74,14 @@
ul {
list-style: none;
padding: 0;
display: flex;
flex-direction: column;
align-items: flex-start;
display: grid;
grid-template-columns: 1fr;
gap: 3px;
font-size: 18px;
}
li {
padding: 5px 10px;
background: #eee;
border-radius: 2px;
margin: 3px 0;
cursor: pointer;
display: grid;
}
li:hover {
@ -93,9 +89,16 @@
color: white;
}
.selected {
button {
border: none;
border-radius: 2px;
padding: 2px;
}
.selected > button {
background: #ff3e00;
color: white;
font-weight: bold;
}
h3 {

@ -83,7 +83,7 @@
"posttest": "agadoo src/internal/index.js",
"prepublishOnly": "pnpm build",
"types": "node ./scripts/generate-dts.js",
"lint": "prettier . --cache --plugin-search-dir=. --check && eslint \"{src,test}/**/*.{ts,js}\" --cache"
"lint": "prettier . --cache --plugin-search-dir=. --check && eslint \"{scripts,src,test}/**/*.js\" --cache --fix"
},
"repository": {
"type": "git",
@ -129,6 +129,7 @@
"agadoo": "^3.0.0",
"dts-buddy": "^0.1.7",
"esbuild": "^0.18.11",
"eslint-plugin-lube": "^0.1.7",
"happy-dom": "^9.20.3",
"jsdom": "22.0.0",
"kleur": "^4.1.5",

@ -0,0 +1,6 @@
{
"plugins": ["lube"],
"rules": {
"lube/svelte-naming-convention": ["error", { "fixSameNames": true }]
}
}

@ -1,8 +1,8 @@
// Compile all Svelte files in a directory to JS and CSS files
// Usage: node scripts/compile-test.js <directory>
import { mkdirSync, readFileSync, writeFileSync } from 'fs';
import path from 'path';
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import glob from 'tiny-glob/sync.js';
import { compile } from '../src/compiler/index.js';

@ -1,18 +1,18 @@
import * as fs from 'fs';
import * as fs from 'node:fs';
import { createBundle } from 'dts-buddy';
// It may look weird, but the imports MUST be ending with index.js to be properly resolved in all TS modes
for (const name of ['action', 'animate', 'easing', 'motion', 'store', 'transition']) {
fs.writeFileSync(`${name}.d.ts`, `import './types/index.js';`);
fs.writeFileSync(`${name}.d.ts`, "import './types/index.js';");
}
fs.writeFileSync('index.d.ts', `import './types/index.js';`);
fs.writeFileSync('compiler.d.ts', `import './types/index.js';`);
fs.writeFileSync('index.d.ts', "import './types/index.js';");
fs.writeFileSync('compiler.d.ts', "import './types/index.js';");
// TODO: some way to mark these as deprecated
fs.mkdirSync('./types/compiler', { recursive: true });
fs.writeFileSync('./types/compiler/preprocess.d.ts', `import '../index.js';`);
fs.writeFileSync('./types/compiler/interfaces.d.ts', `import '../index.js';`);
fs.writeFileSync('./types/compiler/preprocess.d.ts', "import '../index.js';");
fs.writeFileSync('./types/compiler/interfaces.d.ts', "import '../index.js';");
await createBundle({
output: 'types/index.d.ts',

@ -6,8 +6,8 @@ Please run `node scripts/globals-extractor.js` at the project root.
see: https://github.com/microsoft/TypeScript/tree/main/lib
---------------------------------------------------------------------- */
import http from 'https';
import fs from 'fs';
import http from 'node:https';
import fs from 'node:fs';
const GLOBAL_TS_PATH = './src/compiler/utils/globals.js';

@ -0,0 +1,6 @@
{
"plugins": ["lube"],
"rules": {
"lube/svelte-naming-convention": ["error", { "fixSameNames": true }]
}
}

@ -1555,8 +1555,8 @@ export default class Component {
}, [])
);
if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]);
const declaration = declarationList[0];
const declaration_list = lookup.get(cycle[0]);
const declaration = declaration_list[0];
return this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle));
}

@ -4,7 +4,7 @@ import { b } from 'code-red';
* @param {any} program
* @param {import('estree').Identifier} name
* @param {string} banner
* @param {any} sveltePath
* @param {any} svelte_path
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
* @param {import('estree').ImportDeclaration[]} imports
@ -15,21 +15,21 @@ export default function create_module(
program,
name,
banner,
sveltePath = 'svelte',
svelte_path = 'svelte',
helpers,
globals,
imports,
module_exports,
exports_from
) {
const internal_path = `${sveltePath}/internal`;
const internal_path = `${svelte_path}/internal`;
helpers.sort((a, b) => (a.name < b.name ? -1 : 1));
globals.sort((a, b) => (a.name < b.name ? -1 : 1));
return esm(
program,
name,
banner,
sveltePath,
svelte_path,
internal_path,
helpers,
globals,
@ -41,11 +41,11 @@ export default function create_module(
/**
* @param {any} source
* @param {any} sveltePath
* @param {any} svelte_path
*/
function edit_source(source, sveltePath) {
function edit_source(source, svelte_path) {
return source === 'svelte' || source.startsWith('svelte/')
? source.replace('svelte', sveltePath)
? source.replace('svelte', svelte_path)
: source;
}
@ -84,7 +84,7 @@ function get_internal_globals(globals, helpers) {
* @param {any} program
* @param {import('estree').Identifier} name
* @param {string} banner
* @param {string} sveltePath
* @param {string} svelte_path
* @param {string} internal_path
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
@ -96,7 +96,7 @@ function esm(
program,
name,
banner,
sveltePath,
svelte_path,
internal_path,
helpers,
globals,
@ -118,7 +118,7 @@ function esm(
/** @param {any} node */
function rewrite_import(node) {
const value = edit_source(node.source.value, sveltePath);
const value = edit_source(node.source.value, svelte_path);
if (node.source.value !== value) {
node.source.value = value;
node.source.raw = null;

@ -98,7 +98,7 @@ export default class Binding extends Node {
this.is_readonly =
regex_dimensions.test(this.name) ||
regex_box_size.test(this.name) ||
(isElement(parent) &&
(is_element(parent) &&
((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
(parent.name === 'input' && type === 'file'))) /* TODO others? */;
}
@ -127,6 +127,6 @@ export default class Binding extends Node {
* @param {import('./shared/Node.js').default} node
* @returns {node is import('./Element.js').default}
*/
function isElement(node) {
function is_element(node) {
return !!(/** @type {any} */ (node).is_media_node);
}

@ -84,7 +84,9 @@ export default class EachBlock extends AbstractBlock {
this.has_animation = false;
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
if (this.has_animation) {
this.children = this.children.filter((child) => !isEmptyNode(child) && !isCommentNode(child));
this.children = this.children.filter(
(child) => !is_empty_node(child) && !is_comment_node(child)
);
if (this.children.length !== 1) {
const child = this.children.find(
(child) => !!(/** @type {import('./Element.js').default} */ (child).animation)
@ -102,11 +104,11 @@ export default class EachBlock extends AbstractBlock {
}
/** @param {import('./interfaces.js').INode} node */
function isEmptyNode(node) {
function is_empty_node(node) {
return node.type === 'Text' && node.data.trim() === '';
}
/** @param {import('./interfaces.js').INode} node */
function isCommentNode(node) {
function is_comment_node(node) {
return node.type === 'Comment';
}

@ -875,7 +875,7 @@ export default class Element extends Node {
) {
const interactive_handlers = handlers
.map((handler) => handler.name)
.filter((handlerName) => a11y_interactive_handlers.has(handlerName));
.filter((handler_name) => a11y_interactive_handlers.has(handler_name));
if (interactive_handlers.length > 0) {
component.warn(
this,

@ -73,8 +73,8 @@ function sort_consts_nodes(consts_nodes, component) {
}, [])
);
if (cycle && cycle.length) {
const nodeList = lookup.get(cycle[0]);
const node = nodeList[0];
const node_list = lookup.get(cycle[0]);
const node = node_list[0];
component.error(node.node, compiler_errors.cyclical_const_tags(cycle));
}

@ -94,14 +94,14 @@ export default class WindowWrapper extends Wrapper {
bindings.scrollX && bindings.scrollY
? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state`
: x`"${bindings.scrollX || bindings.scrollY}" in this._state`;
const scrollX = bindings.scrollX && x`this._state.${bindings.scrollX}`;
const scrollY = bindings.scrollY && x`this._state.${bindings.scrollY}`;
const scroll_x = bindings.scrollX && x`this._state.${bindings.scrollX}`;
const scroll_y = bindings.scrollY && x`this._state.${bindings.scrollY}`;
renderer.meta_bindings.push(b`
if (${condition}) {
@_scrollTo(${scrollX || '@_window.pageXOffset'}, ${scrollY || '@_window.pageYOffset'});
@_scrollTo(${scroll_x || '@_window.pageXOffset'}, ${scroll_y || '@_window.pageYOffset'});
}
${scrollX && `${scrollX} = @_window.pageXOffset;`}
${scrollY && `${scrollY} = @_window.pageYOffset;`}
${scroll_x && `${scroll_x} = @_window.pageXOffset;`}
${scroll_y && `${scroll_y} = @_window.pageYOffset;`}
`);
block.event_listeners.push(x`
@listen(@_window, "${event}", () => {
@ -132,17 +132,17 @@ export default class WindowWrapper extends Wrapper {
// special case... might need to abstract this out if we add more special cases
if (bindings.scrollX || bindings.scrollY) {
const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean));
const scrollX = bindings.scrollX
const scroll_x = bindings.scrollX
? renderer.reference(bindings.scrollX)
: x`@_window.pageXOffset`;
const scrollY = bindings.scrollY
const scroll_y = bindings.scrollY
? renderer.reference(bindings.scrollY)
: x`@_window.pageYOffset`;
block.chunks.update.push(b`
if (${condition} && !${scrolling}) {
${scrolling} = true;
@_clearTimeout(${scrolling_timeout});
@_scrollTo(${scrollX}, ${scrollY});
@_scrollTo(${scroll_x}, ${scroll_y});
${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100);
}
`);

@ -144,9 +144,13 @@ export default function ssr(component, options) {
? b`
let $$settled;
let $$rendered;
let #previous_head = $$result.head;
do {
$$settled = true;
// $$result.head is mutated by the literal expression
// need to reset it if we're looping back to prevent duplication
$$result.head = #previous_head;
${reactive_declarations}

@ -8,7 +8,7 @@ import * as node from './node/index.js';
*
* The new nodes are located in `./node`.
*/
const cqSyntax = fork({
const cq_syntax = fork({
atrule: {
// extend or override at-rule dictionary
container: {
@ -16,8 +16,8 @@ const cqSyntax = fork({
prelude() {
return this.createSingleNodeList(this.ContainerQuery());
},
block(isStyleBlock = false) {
return this.Block(isStyleBlock);
block(is_style_block = false) {
return this.Block(is_style_block);
}
}
}
@ -25,4 +25,4 @@ const cqSyntax = fork({
node
});
export const parse = cqSyntax.parse;
export const parse = cq_syntax.parse;

@ -16,7 +16,7 @@ export const structure = {
value: ['Identifier', 'Number', 'Comparison', 'Dimension', 'QueryCSSFunction', 'Ratio', null]
};
function lookup_non_WS_type_and_value(offset, type, referenceStr) {
function lookup_non_ws_type_and_value(offset, type, reference_str) {
let current_type;
do {
@ -26,7 +26,7 @@ function lookup_non_WS_type_and_value(offset, type, referenceStr) {
}
} while (current_type !== 0); // NULL -> 0
return current_type === type ? this.lookupValue(offset - 1, referenceStr) : false;
return current_type === type ? this.lookupValue(offset - 1, reference_str) : false;
}
export function parse() {
@ -40,7 +40,7 @@ export function parse() {
while (!this.eof && this.tokenType !== RightParenthesis) {
switch (this.tokenType) {
case Number:
if (lookup_non_WS_type_and_value.call(this, 1, Delim, '/')) {
if (lookup_non_ws_type_and_value.call(this, 1, Delim, '/')) {
child = this.Ratio();
} else {
child = this.Number();

@ -283,7 +283,7 @@ if (typeof HTMLElement === 'function') {
'toAttribute'
);
if (attribute_value == null) {
this.removeAttribute(key);
this.removeAttribute(this.$$p_d[key].attribute || key);
} else {
this.setAttribute(this.$$p_d[key].attribute || key, attribute_value);
}

@ -1,5 +1,7 @@
import { ResizeObserverSingleton } from './ResizeObserverSingleton.js';
import { contenteditable_truthy_values, has_prop } from './utils.js';
import { ResizeObserverSingleton } from './ResizeObserverSingleton.js';
// Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM
// at the end of hydration without touching the remaining nodes.
let is_hydrating = false;
@ -50,14 +52,14 @@ function init_hydrate(target) {
let children = /** @type {ArrayLike<NodeEx2>} */ (target.childNodes);
// If target is <head>, there may be children without claim_order
if (target.nodeName === 'HEAD') {
const myChildren = [];
const my_children = [];
for (let i = 0; i < children.length; i++) {
const node = children[i];
if (node.claim_order !== undefined) {
myChildren.push(node);
my_children.push(node);
}
}
children = myChildren;
children = my_children;
}
/*
* Reorder claimed children optimally.
@ -87,15 +89,15 @@ function init_hydrate(target) {
// Find the largest subsequence length such that it ends in a value less than our current value
// upper_bound returns first greater value, so we subtract one
// with fast path for when we are on the current longest subsequence
const seqLen =
const seq_len =
(longest > 0 && children[m[longest]].claim_order <= current
? longest + 1
: upper_bound(1, longest, (idx) => children[m[idx]].claim_order, current)) - 1;
p[i] = m[seqLen] + 1;
const newLen = seqLen + 1;
p[i] = m[seq_len] + 1;
const new_len = seq_len + 1;
// We can guarantee that current is the smallest value. Otherwise, we would have generated a longer sequence.
m[newLen] = i;
longest = Math.max(newLen, longest);
m[new_len] = i;
longest = Math.max(new_len, longest);
}
// The longest increasing subsequence of nodes (initially reversed)
@ -108,28 +110,28 @@ function init_hydrate(target) {
/**
* @type {NodeEx2[]}
*/
const toMove = [];
const to_move = [];
let last = children.length - 1;
for (let cur = m[longest] + 1; cur != 0; cur = p[cur - 1]) {
lis.push(children[cur - 1]);
for (; last >= cur; last--) {
toMove.push(children[last]);
to_move.push(children[last]);
}
last--;
}
for (; last >= 0; last--) {
toMove.push(children[last]);
to_move.push(children[last]);
}
lis.reverse();
// We sort the nodes being moved to guarantee that their insertion order matches the claim order
toMove.sort((a, b) => a.claim_order - b.claim_order);
to_move.sort((a, b) => a.claim_order - b.claim_order);
// Finally, we move the nodes
for (let i = 0, j = 0; i < toMove.length; i++) {
while (j < lis.length && toMove[i].claim_order >= lis[j].claim_order) {
for (let i = 0, j = 0; i < to_move.length; i++) {
while (j < lis.length && to_move[i].claim_order >= lis[j].claim_order) {
j++;
}
const anchor = j < lis.length ? lis[j] : null;
target.insertBefore(toMove[i], anchor);
target.insertBefore(to_move[i], anchor);
}
}
@ -624,26 +626,26 @@ function init_claim_info(nodes) {
* @template {ChildNodeEx} R
* @param {ChildNodeArray} nodes
* @param {(node: ChildNodeEx) => node is R} predicate
* @param {(node: ChildNodeEx) => ChildNodeEx | undefined} processNode
* @param {() => R} createNode
* @param {boolean} dontUpdateLastIndex
* @param {(node: ChildNodeEx) => ChildNodeEx | undefined} process_node
* @param {() => R} create_node
* @param {boolean} dont_update_last_index
* @returns {R}
*/
function claim_node(nodes, predicate, processNode, createNode, dontUpdateLastIndex = false) {
function claim_node(nodes, predicate, process_node, create_node, dont_update_last_index = false) {
// Try to find nodes in an order such that we lengthen the longest increasing subsequence
init_claim_info(nodes);
const resultNode = (() => {
const result_node = (() => {
// We first try to find an element after the previous one
for (let i = nodes.claim_info.last_index; i < nodes.length; i++) {
const node = nodes[i];
if (predicate(node)) {
const replacement = processNode(node);
const replacement = process_node(node);
if (replacement === undefined) {
nodes.splice(i, 1);
} else {
nodes[i] = replacement;
}
if (!dontUpdateLastIndex) {
if (!dont_update_last_index) {
nodes.claim_info.last_index = i;
}
return node;
@ -654,13 +656,13 @@ function claim_node(nodes, predicate, processNode, createNode, dontUpdateLastInd
for (let i = nodes.claim_info.last_index - 1; i >= 0; i--) {
const node = nodes[i];
if (predicate(node)) {
const replacement = processNode(node);
const replacement = process_node(node);
if (replacement === undefined) {
nodes.splice(i, 1);
} else {
nodes[i] = replacement;
}
if (!dontUpdateLastIndex) {
if (!dont_update_last_index) {
nodes.claim_info.last_index = i;
} else if (replacement === undefined) {
// Since we spliced before the last_index, we decrease it
@ -670,11 +672,11 @@ function claim_node(nodes, predicate, processNode, createNode, dontUpdateLastInd
}
}
// If we can't find any matching node, we create a new one
return createNode();
return create_node();
})();
resultNode.claim_order = nodes.claim_info.total_claimed;
result_node.claim_order = nodes.claim_info.total_claimed;
nodes.claim_info.total_claimed += 1;
return resultNode;
return result_node;
}
/**
@ -736,13 +738,13 @@ export function claim_text(nodes, data) {
(node) => node.nodeType === 3,
/** @param {Text} node */
(node) => {
const dataStr = '' + data;
if (node.data.startsWith(dataStr)) {
if (node.data.length !== dataStr.length) {
return node.splitText(dataStr.length);
const data_str = '' + data;
if (node.data.startsWith(data_str)) {
if (node.data.length !== data_str.length) {
return node.splitText(data_str.length);
}
} else {
node.data = dataStr;
node.data = data_str;
}
},
() => text(data),

@ -1,6 +1,8 @@
{
"plugins": ["lube"],
"rules": {
"no-console": "off",
"@typescript-eslint/no-var-requires": "off"
"@typescript-eslint/no-var-requires": "off",
"lube/svelte-naming-convention": ["error", { "fixSameNames": true }]
}
}

@ -20,14 +20,14 @@ describe('compiler-errors', () => {
it_fn(dir, () => {
const cwd = path.resolve(`${__dirname}/samples/${dir}`);
const compileOptions = Object.assign({}, config.compileOptions || {}, {
const compile_options = Object.assign({}, config.compileOptions || {}, {
immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true,
generate: 'dom'
});
try {
compile(fs.readFileSync(`${cwd}/main.svelte`, 'utf-8'), compileOptions);
compile(fs.readFileSync(`${cwd}/main.svelte`, 'utf-8'), compile_options);
} catch (error) {
if (typeof config.error === 'function') {
config.error(assert, error);

@ -2,11 +2,11 @@ export default {
compileOptions: {
filename: 'src/components/FooSwitcher.svelte',
cssHash({ hash, css, name, filename }) {
const minFilename = filename
const min_filename = filename
.split('/')
.map((i) => i.charAt(0).toLowerCase())
.join('');
return `sv-${name}-${minFilename}-${hash(css)}`;
return `sv-${name}-${min_filename}-${hash(css)}`;
}
}
};

@ -1,10 +1,11 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import glob from 'tiny-glob/sync';
import colors from 'kleur';
import { assert } from 'vitest';
import colors from 'kleur';
import { compile } from 'svelte/compiler';
import { fileURLToPath } from 'node:url';
import glob from 'tiny-glob/sync';
export function try_load_json(file) {
try {
@ -158,8 +159,8 @@ export function create_loader(compileOptions, cwd) {
)
.replace(
/^import (\w+, )?{([^}]+)} from ['"](.+)['"];?/gm,
(_, default_, names, source) => {
const d = default_ ? `default: ${default_}` : '';
(_, _default, names, source) => {
const d = _default ? `default: ${_default}` : '';
return `const { ${d} ${names.replaceAll(
' as ',
': '

@ -18,12 +18,12 @@ describe('hydration', async () => {
it_fn(dir, async () => {
const cwd = path.resolve(`${__dirname}/samples/${dir}`);
const compileOptions = Object.assign({}, config.compileOptions, {
const compile_options = Object.assign({}, config.compileOptions, {
accessors: 'accessors' in config ? config.accessors : true,
hydratable: true
});
const { default: SvelteComponent } = await create_loader(compileOptions, cwd)('main.svelte');
const { default: SvelteComponent } = await create_loader(compile_options, cwd)('main.svelte');
const target = window.document.body;
const head = window.document.head;

@ -2,12 +2,12 @@ export default {
props: {},
snapshot(target) {
const nullText = target.querySelectorAll('p')[0].textContent;
const undefinedText = target.querySelectorAll('p')[1].textContent;
const null_text = target.querySelectorAll('p')[0].textContent;
const undefined_text = target.querySelectorAll('p')[1].textContent;
return {
nullText,
undefinedText
nullText: null_text,
undefinedText: undefined_text
};
}
};

@ -26,8 +26,8 @@ describe('parse', () => {
.trimEnd()
.replace(/\r/g, '');
const expectedOutput = try_load_json(`${__dirname}/samples/${dir}/output.json`);
const expectedError = try_load_json(`${__dirname}/samples/${dir}/error.json`);
const expected_output = try_load_json(`${__dirname}/samples/${dir}/output.json`);
const expected_error = try_load_json(`${__dirname}/samples/${dir}/error.json`);
try {
const { ast } = svelte.compile(
@ -42,16 +42,16 @@ describe('parse', () => {
JSON.stringify(ast, null, '\t')
);
assert.deepEqual(ast.html, expectedOutput.html);
assert.deepEqual(ast.css, expectedOutput.css);
assert.deepEqual(ast.instance, expectedOutput.instance);
assert.deepEqual(ast.module, expectedOutput.module);
assert.deepEqual(ast.html, expected_output.html);
assert.deepEqual(ast.css, expected_output.css);
assert.deepEqual(ast.instance, expected_output.instance);
assert.deepEqual(ast.module, expected_output.module);
} catch (err) {
if (err.name !== 'ParseError') throw err;
if (!expectedError) throw err;
if (!expected_error) throw err;
const { code, message, pos, start } = err;
assert.deepEqual({ code, message, pos, start }, expectedError);
assert.deepEqual({ code, message, pos, start }, expected_error);
}
});
});

@ -33,10 +33,10 @@ export function ok(condition, message) {
}
export function htmlEqual(actual, expected, message) {
return deepEqual(normalizeHtml(window, actual), normalizeHtml(window, expected), message);
return deepEqual(normalize_html(window, actual), normalize_html(window, expected), message);
}
function normalizeHtml(window, html) {
function normalize_html(window, html) {
try {
const node = window.document.createElement('div');
node.innerHTML = html
@ -44,7 +44,7 @@ function normalizeHtml(window, html) {
.replace(/>[\s\r\n]+</g, '><')
.trim();
normalizeStyles(node);
normalize_styles(node);
return node.innerHTML.replace(/<\/?noscript\/?>/g, '');
} catch (err) {
@ -52,14 +52,14 @@ function normalizeHtml(window, html) {
}
}
function normalizeStyles(node) {
function normalize_styles(node) {
if (node.nodeType === 1) {
if (node.hasAttribute('style')) {
node.style = node.style.cssText;
}
for (const child of node.childNodes) {
normalizeStyles(child);
normalize_styles(child);
}
}
}

@ -93,7 +93,7 @@ async function run_browser_test(dir) {
globalName: 'test'
});
function assertWarnings() {
function assert_warnings() {
if (config.warnings) {
assert.deepStrictEqual(
warnings.map((w) => ({
@ -112,7 +112,7 @@ async function run_browser_test(dir) {
}
}
assertWarnings();
assert_warnings();
try {
const page = await browser.newPage();
@ -191,7 +191,7 @@ async function run_custom_elements_test(dir) {
globalName: 'test'
});
function assertWarnings() {
function assert_warnings() {
if (expected_warnings) {
assert.deepStrictEqual(
warnings.map((w) => ({
@ -205,7 +205,7 @@ async function run_custom_elements_test(dir) {
);
}
}
assertWarnings();
assert_warnings();
const page = await browser.newPage();
page.on('console', (type) => {

@ -0,0 +1,23 @@
<svelte:options
customElement={{
tag: 'custom-element',
props: {
expanded: { reflect: true, type: 'Boolean', attribute: 'aria-expanded' }
}
}}
/>
<script>
export let expanded = false;
</script>
<div>
<button on:click={() => (expanded = !expanded)}>Toggle</button>
<div class:hidden={!expanded}>Hidden Text</div>
</div>
<style>
.hidden {
display: none;
}
</style>

@ -0,0 +1,19 @@
import * as assert from 'assert.js';
import { tick } from 'svelte';
import './main.svelte';
export default async function (target) {
const element = document.createElement('custom-element');
target.appendChild(element);
await tick();
const el = target.querySelector('custom-element');
el.shadowRoot.querySelector('button').click();
await tick();
assert.equal(el.getAttribute('aria-expanded'), '');
el.shadowRoot.querySelector('button').click();
await tick();
assert.equal(el.getAttribute('aria-expanded'), null);
}

@ -6,25 +6,25 @@ export default async function (target) {
target.innerHTML = '<custom-element red white></custom-element>';
await tick();
await tick();
const ceRoot = target.querySelector('custom-element').shadowRoot;
const div = ceRoot.querySelector('div');
const p = ceRoot.querySelector('p');
const button = ceRoot.querySelector('button');
const ce_root = target.querySelector('custom-element').shadowRoot;
const div = ce_root.querySelector('div');
const p = ce_root.querySelector('p');
const button = ce_root.querySelector('button');
assert.equal(getComputedStyle(div).color, 'rgb(255, 0, 0)');
assert.equal(getComputedStyle(p).color, 'rgb(255, 255, 255)');
const innerRoot = ceRoot.querySelector('my-widget').shadowRoot;
const innerDiv = innerRoot.querySelector('div');
const innerP = innerRoot.querySelector('p');
const inner_root = ce_root.querySelector('my-widget').shadowRoot;
const inner_div = inner_root.querySelector('div');
const inner_p = inner_root.querySelector('p');
assert.equal(getComputedStyle(innerDiv).color, 'rgb(255, 0, 0)');
assert.equal(getComputedStyle(innerP).color, 'rgb(255, 255, 255)');
assert.equal(getComputedStyle(inner_div).color, 'rgb(255, 0, 0)');
assert.equal(getComputedStyle(inner_p).color, 'rgb(255, 255, 255)');
button.click();
await tick();
await tick();
assert.equal(getComputedStyle(div).color, 'rgb(0, 0, 0)');
assert.equal(getComputedStyle(innerDiv).color, 'rgb(0, 0, 0)');
assert.equal(getComputedStyle(inner_div).color, 'rgb(0, 0, 0)');
}

@ -30,7 +30,7 @@ export default async function (target) {
const component = new SvelteComponent(options);
const waitUntil = async (fn, ms = 500) => {
const wait_until = async (fn, ms = 500) => {
const start = new Date().getTime();
do {
if (fn()) return;
@ -48,7 +48,7 @@ export default async function (target) {
component,
target,
window,
waitUntil
waitUntil: wait_until
});
component.$destroy();

@ -45,14 +45,14 @@ export default {
`
);
const circleColor1 = target.querySelector('#svg-1 circle');
const rectColor1 = target.querySelector('#svg-1 rect');
const circleColor2 = target.querySelector('#svg-2 circle');
const rectColor2 = target.querySelector('#svg-2 rect');
const circle_color1 = target.querySelector('#svg-1 circle');
const rect_color1 = target.querySelector('#svg-1 rect');
const circle_color2 = target.querySelector('#svg-2 circle');
const rect_color2 = target.querySelector('#svg-2 rect');
assert.htmlEqual(window.getComputedStyle(circleColor1).fill, 'rgb(255, 0, 0)');
assert.htmlEqual(window.getComputedStyle(rectColor1).fill, 'rgb(255, 255, 0)');
assert.htmlEqual(window.getComputedStyle(circleColor2).fill, 'rgb(0, 255, 255)');
assert.htmlEqual(window.getComputedStyle(rectColor2).fill, 'rgb(0, 0, 0)');
assert.htmlEqual(window.getComputedStyle(circle_color1).fill, 'rgb(255, 0, 0)');
assert.htmlEqual(window.getComputedStyle(rect_color1).fill, 'rgb(255, 255, 0)');
assert.htmlEqual(window.getComputedStyle(circle_color2).fill, 'rgb(0, 255, 255)');
assert.htmlEqual(window.getComputedStyle(rect_color2).fill, 'rgb(0, 0, 0)');
}
};

@ -14,14 +14,14 @@ export default {
</div>
`,
test({ target, window, assert }) {
const railColor1 = target.querySelector('#slider-1 p');
const trackColor1 = target.querySelector('#slider-1 span');
const railColor2 = target.querySelector('#slider-2 p');
const trackColor2 = target.querySelector('#slider-2 span');
const rail_color1 = target.querySelector('#slider-1 p');
const track_color1 = target.querySelector('#slider-1 span');
const rail_color2 = target.querySelector('#slider-2 p');
const track_color2 = target.querySelector('#slider-2 span');
assert.htmlEqual(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)');
assert.htmlEqual(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)');
assert.htmlEqual(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)');
assert.htmlEqual(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)');
assert.htmlEqual(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.htmlEqual(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.htmlEqual(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.htmlEqual(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
}
};

@ -18,31 +18,31 @@ export default {
`,
test({ target, window, assert, component }) {
function assert_slider_1() {
const railColor1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span');
const rail_color1 = target.querySelector('#component1 p');
const track_color1 = target.querySelector('#component1 span');
const rail_color2 = target.querySelector('#component2 p');
const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider1');
assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(rail_color2.textContent, 'Slider1');
}
function assert_slider_2() {
const railColor1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span');
const rail_color1 = target.querySelector('#component1 p');
const track_color1 = target.querySelector('#component1 span');
const rail_color2 = target.querySelector('#component2 p');
const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider2');
assert.equal(railColor2.textContent, 'Slider2');
assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(rail_color1.textContent, 'Slider2');
assert.equal(rail_color2.textContent, 'Slider2');
}
assert_slider_1();

@ -20,31 +20,31 @@ export default {
`,
test({ target, window, assert, component }) {
function assert_slider_1() {
const railColor1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span');
const rail_color1 = target.querySelector('#component1 p');
const track_color1 = target.querySelector('#component1 span');
const rail_color2 = target.querySelector('#component2 p');
const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider1');
assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(rail_color2.textContent, 'Slider1');
}
function assert_slider_2() {
const railColor1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span');
const rail_color1 = target.querySelector('#component1 p');
const track_color1 = target.querySelector('#component1 span');
const rail_color2 = target.querySelector('#component2 p');
const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider2');
assert.equal(railColor2.textContent, 'Slider2');
assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(rail_color1.textContent, 'Slider2');
assert.equal(rail_color2.textContent, 'Slider2');
}
assert_slider_1();

@ -22,26 +22,26 @@ export default {
</div>
`,
test({ target, window, assert }) {
const railColor1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span');
const nestRailColor1 = target.querySelector('#nest-component1 p');
const nestTrackColor1 = target.querySelector('#nest-component1 span');
const nestRailColor2 = target.querySelector('#nest-component2 p');
const nestTrackColor2 = target.querySelector('#nest-component2 span');
const rail_color1 = target.querySelector('#component1 p');
const track_color1 = target.querySelector('#component1 span');
const rail_color2 = target.querySelector('#component2 p');
const track_color2 = target.querySelector('#component2 span');
const nest_rail_color1 = target.querySelector('#nest-component1 p');
const nest_track_color1 = target.querySelector('#nest-component1 span');
const nest_rail_color2 = target.querySelector('#nest-component2 p');
const nest_track_color2 = target.querySelector('#nest-component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor1).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nestTrackColor1).color, 'rgb(255, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor2).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nestTrackColor2).color, 'rgb(255, 255, 255)');
assert.equal(railColor1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider2');
assert.equal(nestRailColor1.textContent, 'Slider1');
assert.equal(nestRailColor2.textContent, 'Slider2');
assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nest_rail_color1).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nest_track_color1).color, 'rgb(255, 0, 255)');
assert.equal(window.getComputedStyle(nest_rail_color2).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nest_track_color2).color, 'rgb(255, 255, 255)');
assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(rail_color2.textContent, 'Slider2');
assert.equal(nest_rail_color1.textContent, 'Slider1');
assert.equal(nest_rail_color2.textContent, 'Slider2');
}
};

@ -25,51 +25,51 @@ export default {
`,
test({ target, window, assert, component }) {
function assert_slider_1() {
const railColor1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span');
const nestRailColor1 = target.querySelector('#nest-component1 p');
const nestTrackColor1 = target.querySelector('#nest-component1 span');
const nestRailColor2 = target.querySelector('#nest-component2 p');
const nestTrackColor2 = target.querySelector('#nest-component2 span');
const rail_color1 = target.querySelector('#component1 p');
const track_color1 = target.querySelector('#component1 span');
const rail_color2 = target.querySelector('#component2 p');
const track_color2 = target.querySelector('#component2 span');
const nest_rail_color1 = target.querySelector('#nest-component1 p');
const nest_track_color1 = target.querySelector('#nest-component1 span');
const nest_rail_color2 = target.querySelector('#nest-component2 p');
const nest_track_color2 = target.querySelector('#nest-component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor1).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nestTrackColor1).color, 'rgb(255, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor2).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nestTrackColor2).color, 'rgb(255, 0, 255)');
assert.equal(railColor1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider1');
assert.equal(nestRailColor1.textContent, 'Slider1');
assert.equal(nestRailColor2.textContent, 'Slider1');
assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nest_rail_color1).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nest_track_color1).color, 'rgb(255, 0, 255)');
assert.equal(window.getComputedStyle(nest_rail_color2).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nest_track_color2).color, 'rgb(255, 0, 255)');
assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(rail_color2.textContent, 'Slider1');
assert.equal(nest_rail_color1.textContent, 'Slider1');
assert.equal(nest_rail_color2.textContent, 'Slider1');
}
function assert_slider_2() {
const railColor1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span');
const nestRailColor1 = target.querySelector('#nest-component1 p');
const nestTrackColor1 = target.querySelector('#nest-component1 span');
const nestRailColor2 = target.querySelector('#nest-component2 p');
const nestTrackColor2 = target.querySelector('#nest-component2 span');
const rail_color1 = target.querySelector('#component1 p');
const track_color1 = target.querySelector('#component1 span');
const rail_color2 = target.querySelector('#component2 p');
const track_color2 = target.querySelector('#component2 span');
const nest_rail_color1 = target.querySelector('#nest-component1 p');
const nest_track_color1 = target.querySelector('#nest-component1 span');
const nest_rail_color2 = target.querySelector('#nest-component2 p');
const nest_track_color2 = target.querySelector('#nest-component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor1).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nestTrackColor1).color, 'rgb(255, 255, 255)');
assert.equal(window.getComputedStyle(nestRailColor2).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nestTrackColor2).color, 'rgb(255, 255, 255)');
assert.equal(railColor1.textContent, 'Slider2');
assert.equal(railColor2.textContent, 'Slider2');
assert.equal(nestRailColor1.textContent, 'Slider2');
assert.equal(nestRailColor2.textContent, 'Slider2');
assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nest_rail_color1).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nest_track_color1).color, 'rgb(255, 255, 255)');
assert.equal(window.getComputedStyle(nest_rail_color2).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nest_track_color2).color, 'rgb(255, 255, 255)');
assert.equal(rail_color1.textContent, 'Slider2');
assert.equal(rail_color2.textContent, 'Slider2');
assert.equal(nest_rail_color1.textContent, 'Slider2');
assert.equal(nest_rail_color2.textContent, 'Slider2');
}
assert_slider_1();

@ -55,18 +55,18 @@ async function run_test(dir) {
const cwd = path.resolve(`${__dirname}/samples/${dir}`);
const compileOptions = Object.assign({}, config.compileOptions || {}, {
const compile_options = Object.assign({}, config.compileOptions || {}, {
hydratable: hydrate,
immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true
});
const load = create_loader(compileOptions, cwd);
const load = create_loader(compile_options, cwd);
let mod;
let SvelteComponent;
let unintendedError = null;
let unintended_error = null;
if (config.expect_unhandled_rejections) {
listeners.forEach((listener) => {
@ -111,7 +111,7 @@ async function run_test(dir) {
let snapshot = undefined;
if (hydrate && from_ssr_html) {
const load_ssr = create_loader({ ...compileOptions, generate: 'ssr' }, cwd);
const load_ssr = create_loader({ ...compile_options, generate: 'ssr' }, cwd);
// ssr into target
if (config.before_test) config.before_test();
@ -152,14 +152,14 @@ async function run_test(dir) {
console.warn = warn;
if (config.error) {
unintendedError = true;
unintended_error = true;
assert.fail('Expected a runtime error');
}
if (config.warnings) {
assert.deepEqual(warnings, config.warnings);
} else if (warnings.length) {
unintendedError = true;
unintended_error = true;
assert.fail('Received unexpected warnings');
}
@ -183,7 +183,7 @@ async function run_test(dir) {
snapshot,
window,
raf,
compileOptions,
compileOptions: compile_options,
load
});
}
@ -201,7 +201,7 @@ async function run_test(dir) {
await test()
.catch((err) => {
if (config.error && !unintendedError) {
if (config.error && !unintended_error) {
if (typeof config.error === 'function') {
config.error(assert, err);
} else {
@ -217,7 +217,7 @@ async function run_test(dir) {
mkdirp(path.dirname(out)); // file could be in subdirectory, therefore don't use dir
const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r/g, ''), {
...compileOptions,
...compile_options,
filename: file
});
fs.writeFileSync(out, js.code);

@ -10,9 +10,9 @@ export default {
`,
async test({ assert, target, window }) {
const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click');
const click_event = new window.MouseEvent('click');
await btn1.dispatchEvent(clickEvent);
await btn1.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -24,7 +24,7 @@ export default {
`
);
await btn2.dispatchEvent(clickEvent);
await btn2.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -36,7 +36,7 @@ export default {
`
);
await btn3.dispatchEvent(clickEvent);
await btn3.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -48,7 +48,7 @@ export default {
`
);
await btn4.dispatchEvent(clickEvent);
await btn4.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,

@ -12,9 +12,9 @@ export default {
async test({ assert, target, window }) {
const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click');
const click_event = new window.MouseEvent('click');
await btn1.dispatchEvent(clickEvent);
await btn1.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -27,7 +27,7 @@ export default {
`
);
await btn2.dispatchEvent(clickEvent);
await btn2.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -40,7 +40,7 @@ export default {
`
);
await btn3.dispatchEvent(clickEvent);
await btn3.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -53,7 +53,7 @@ export default {
`
);
await btn4.dispatchEvent(clickEvent);
await btn4.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,

@ -5,10 +5,10 @@ export default {
async test({ assert, target, window }) {
const button = target.querySelector('button');
const eventEnter = new window.MouseEvent('mouseenter');
const eventLeave = new window.MouseEvent('mouseleave');
const event_enter = new window.MouseEvent('mouseenter');
const event_leave = new window.MouseEvent('mouseleave');
await button.dispatchEvent(eventEnter);
await button.dispatchEvent(event_enter);
assert.htmlEqual(
target.innerHTML,
`
@ -17,7 +17,7 @@ export default {
`
);
await button.dispatchEvent(eventLeave);
await button.dispatchEvent(event_leave);
assert.htmlEqual(
target.innerHTML,
`

@ -7,7 +7,7 @@ export default {
const button = target.querySelector('button');
const enter = new window.MouseEvent('mouseenter');
const leave = new window.MouseEvent('mouseleave');
const ctrlPress = new window.KeyboardEvent('keydown', { ctrlKey: true });
const ctrl_press = new window.KeyboardEvent('keydown', { ctrlKey: true });
await button.dispatchEvent(enter);
assert.htmlEqual(
@ -18,7 +18,7 @@ export default {
`
);
await window.dispatchEvent(ctrlPress);
await window.dispatchEvent(ctrl_press);
assert.htmlEqual(
target.innerHTML,
`

@ -5,10 +5,10 @@ export default {
async test({ assert, target, window }) {
const button = target.querySelector('button');
const eventEnter = new window.MouseEvent('mouseenter');
const eventLeave = new window.MouseEvent('mouseleave');
const event_enter = new window.MouseEvent('mouseenter');
const event_leave = new window.MouseEvent('mouseleave');
await button.dispatchEvent(eventEnter);
await button.dispatchEvent(event_enter);
assert.htmlEqual(
target.innerHTML,
`
@ -17,7 +17,7 @@ export default {
`
);
await button.dispatchEvent(eventLeave);
await button.dispatchEvent(event_leave);
assert.htmlEqual(
target.innerHTML,
`

@ -1,8 +1,8 @@
const realPromise = Promise.resolve(42);
const real_promise = Promise.resolve(42);
const promise = () => {};
promise.then = realPromise.then.bind(realPromise);
promise.catch = realPromise.catch.bind(realPromise);
promise.then = real_promise.then.bind(real_promise);
promise.catch = real_promise.catch.bind(real_promise);
export default {
get props() {

@ -1,13 +1,13 @@
let fulfil;
const thePromise = new Promise((f) => {
const the_promise = new Promise((f) => {
fulfil = f;
});
const items = [
{
title: 'a title',
data: thePromise
data: the_promise
}
];
@ -23,7 +23,7 @@ export default {
test({ assert, target }) {
fulfil(42);
return thePromise.then(() => {
return the_promise.then(() => {
assert.htmlEqual(
target.innerHTML,
`

@ -28,10 +28,10 @@ export default {
assert.equal(component.clicked, 42);
const thePromise = Promise.resolve(43);
component.thePromise = thePromise;
const the_promise = Promise.resolve(43);
component.thePromise = the_promise;
return thePromise;
return the_promise;
})
.then(() => {
const { button } = component;

@ -1,12 +1,12 @@
let fulfil;
const thePromise = new Promise((f) => {
const the_promise = new Promise((f) => {
fulfil = f;
});
export default {
get props() {
return { show: true, thePromise };
return { show: true, thePromise: the_promise };
},
html: `
@ -16,7 +16,7 @@ export default {
test({ assert, component, target }) {
fulfil(42);
return thePromise.then(() => {
return the_promise.then(() => {
assert.htmlEqual(
target.innerHTML,
`
@ -35,7 +35,7 @@ export default {
component.show = true;
return thePromise.then(() => {
return the_promise.then(() => {
assert.htmlEqual(
target.innerHTML,
`

@ -1,12 +1,12 @@
let fulfil;
const thePromise = new Promise((f) => {
const the_promise = new Promise((f) => {
fulfil = f;
});
export default {
get props() {
return { thePromise };
return { thePromise: the_promise };
},
html: `
@ -16,7 +16,7 @@ export default {
test({ assert, target }) {
fulfil(42);
return thePromise.then(() => {
return the_promise.then(() => {
assert.htmlEqual(
target.innerHTML,
`

@ -1,12 +1,12 @@
let fulfil;
const thePromise = new Promise((f) => {
const the_promise = new Promise((f) => {
fulfil = f;
});
export default {
get props() {
return { thePromise };
return { thePromise: the_promise };
},
html: `
@ -16,7 +16,7 @@ export default {
async test({ assert, target }) {
fulfil([]);
await thePromise;
await the_promise;
assert.htmlEqual(
target.innerHTML,

@ -15,12 +15,12 @@ export default {
test({ assert, component, target, window }) {
const { cats } = component;
const newCats = cats.slice();
newCats.push({
const new_cats = cats.slice();
new_cats.push({
name: 'cat ' + cats.length,
checked: false
});
component.cats = newCats;
component.cats = new_cats;
let inputs = target.querySelectorAll('input');
assert.equal(inputs.length, 3);

@ -23,7 +23,7 @@ export default {
async test({ assert, target, window }) {
const inputs = target.querySelectorAll('input');
const checked = new Set();
const checkInbox = async (i) => {
const check_inbox = async (i) => {
checked.add(i);
inputs[i].checked = true;
await inputs[i].dispatchEvent(event);
@ -35,17 +35,17 @@ export default {
const event = new window.Event('change');
await checkInbox(2);
await check_inbox(2);
for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i));
}
await checkInbox(12);
await check_inbox(12);
for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i));
}
await checkInbox(8);
await check_inbox(8);
for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i));
}

@ -1,8 +1,8 @@
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 click_event = new window.Event('click');
const change_event = new window.Event('change');
const [input1, input2] = target.querySelectorAll('input[type="checkbox"]');
function validate_inputs(v1, v2) {
@ -17,24 +17,24 @@ export default {
validate_inputs(true, true);
input1.checked = false;
await input1.dispatchEvent(changeEvent);
await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, ['b']);
input2.checked = false;
await input2.dispatchEvent(changeEvent);
await input2.dispatchEvent(change_event);
assert.deepEqual(component.test, []);
input1.checked = true;
input2.checked = true;
await input1.dispatchEvent(changeEvent);
await input2.dispatchEvent(changeEvent);
await input1.dispatchEvent(change_event);
await input2.dispatchEvent(change_event);
assert.deepEqual(component.test, ['b', 'a']);
await button.dispatchEvent(clickEvent);
await button.dispatchEvent(click_event);
assert.deepEqual(component.test, ['b', 'a']); // should it be ['a'] only? valid arguments for both outcomes
input1.checked = false;
await input1.dispatchEvent(changeEvent);
await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, []);
}
};

@ -1,8 +1,8 @@
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 click_event = new window.Event('click');
const change_event = new window.Event('change');
const [input1, input2] = target.querySelectorAll('input[type="radio"]');
function validate_inputs(v1, v2) {
@ -17,18 +17,18 @@ export default {
validate_inputs(false, true);
input1.checked = true;
await input1.dispatchEvent(changeEvent);
await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, 'a');
input2.checked = true;
await input2.dispatchEvent(changeEvent);
await input2.dispatchEvent(change_event);
assert.deepEqual(component.test, 'b');
await button.dispatchEvent(clickEvent);
await button.dispatchEvent(click_event);
assert.deepEqual(component.test, 'b'); // should it be undefined? valid arguments for both outcomes
input1.checked = true;
await input1.dispatchEvent(changeEvent);
await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, 'a');
}
};

@ -1,25 +1,25 @@
export default {
test({ assert, target, window, component }) {
const input = target.querySelector('input');
const inputEvent = new window.InputEvent('input');
const input_event = new window.InputEvent('input');
assert.equal(component.value, 5);
assert.equal(input.value, '5');
input.value = '5.';
input.dispatchEvent(inputEvent);
input.dispatchEvent(input_event);
// input type number has value === "" if ends with dot/comma
assert.equal(component.value, undefined);
assert.equal(input.value, '');
input.value = '5.5';
input.dispatchEvent(inputEvent);
input.dispatchEvent(input_event);
assert.equal(component.value, 5.5);
assert.equal(input.value, '5.5');
input.value = '5.50';
input.dispatchEvent(inputEvent);
input.dispatchEvent(input_event);
assert.equal(component.value, 5.5);
assert.equal(input.value, '5.50');

@ -9,13 +9,13 @@ const components = [
}
];
const selectedComponent = components[0];
const selected_component = components[0];
export default {
skip: true, // doesn't reflect real-world bug, maybe a JSDOM quirk
get props() {
return { components, selectedComponent };
return { components, selectedComponent: selected_component };
},
html: `

@ -5,13 +5,13 @@ export default {
`,
async test({ assert, component, target, window }) {
const [updateButton, button] = target.querySelectorAll('button');
const [update_button, button] = target.querySelectorAll('button');
const event = new window.MouseEvent('click');
await button.dispatchEvent(event);
assert.equal(component.count, 1);
await updateButton.dispatchEvent(event);
await update_button.dispatchEvent(event);
await button.dispatchEvent(event);
assert.equal(component.count, 11);
}

@ -5,10 +5,10 @@ export default {
`,
async test({ assert, component, target, window }) {
const [updateButton, button] = target.querySelectorAll('button');
const [update_button, button] = target.querySelectorAll('button');
const event = new window.MouseEvent('click');
await updateButton.dispatchEvent(event);
await update_button.dispatchEvent(event);
await button.dispatchEvent(event);
assert.equal(component.count, 10);

@ -3,7 +3,7 @@ export default {
ssrHtml: '<input value="Blub"> <input value="Blub"> <input value="Blub">',
async test({ assert, target, component, window }) {
const [input1, input2, inputFallback] = target.querySelectorAll('input');
const [input1, input2, input_fallback] = target.querySelectorAll('input');
assert.equal(component.getSubscriberCount(), 3);
@ -13,7 +13,7 @@ export default {
await input1.dispatchEvent(new window.Event('input'));
assert.equal(input1.value, 'ab');
assert.equal(input2.value, 'ab');
assert.equal(inputFallback.value, 'ab');
assert.equal(input_fallback.value, 'ab');
component.props = 'hello';

@ -6,9 +6,9 @@ export default {
async test({ assert, target, window }) {
const btn = target.querySelector('button');
const clickEvent = new window.MouseEvent('click');
const click_event = new window.MouseEvent('click');
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -18,7 +18,7 @@ export default {
`
);
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,

@ -1,6 +1,6 @@
let originalDivGetBoundingClientRect;
let originalSpanGetBoundingClientRect;
let originalParagraphGetBoundingClientRect;
let original_div_get_bounding_client_rect;
let original_span_get_bounding_client_rect;
let original_paragraph_get_bounding_client_rect;
export default {
skip_if_ssr: true,
@ -26,16 +26,16 @@ export default {
`,
before_test() {
originalDivGetBoundingClientRect = window.HTMLDivElement.prototype.getBoundingClientRect;
originalSpanGetBoundingClientRect = window.HTMLSpanElement.prototype.getBoundingClientRect;
originalParagraphGetBoundingClientRect =
original_div_get_bounding_client_rect = window.HTMLDivElement.prototype.getBoundingClientRect;
original_span_get_bounding_client_rect = window.HTMLSpanElement.prototype.getBoundingClientRect;
original_paragraph_get_bounding_client_rect =
window.HTMLParagraphElement.prototype.getBoundingClientRect;
window.HTMLDivElement.prototype.getBoundingClientRect = fakeGetBoundingClientRect;
window.HTMLSpanElement.prototype.getBoundingClientRect = fakeGetBoundingClientRect;
window.HTMLParagraphElement.prototype.getBoundingClientRect = fakeGetBoundingClientRect;
window.HTMLDivElement.prototype.getBoundingClientRect = fake_get_bounding_client_rect;
window.HTMLSpanElement.prototype.getBoundingClientRect = fake_get_bounding_client_rect;
window.HTMLParagraphElement.prototype.getBoundingClientRect = fake_get_bounding_client_rect;
function fakeGetBoundingClientRect() {
function fake_get_bounding_client_rect() {
const index = [...this.parentNode.children].indexOf(this);
const top = index * 30;
@ -48,10 +48,10 @@ export default {
}
},
after_test() {
window.HTMLDivElement.prototype.getBoundingClientRect = originalDivGetBoundingClientRect;
window.HTMLSpanElement.prototype.getBoundingClientRect = originalSpanGetBoundingClientRect;
window.HTMLDivElement.prototype.getBoundingClientRect = original_div_get_bounding_client_rect;
window.HTMLSpanElement.prototype.getBoundingClientRect = original_span_get_bounding_client_rect;
window.HTMLParagraphElement.prototype.getBoundingClientRect =
originalParagraphGetBoundingClientRect;
original_paragraph_get_bounding_client_rect;
},
async test({ assert, component, raf }) {

@ -13,10 +13,10 @@ export default {
assert.equal(input1.value, '');
assert.equal(input2.value, 'hello');
const inputEvent = new window.InputEvent('input');
const input_event = new window.InputEvent('input');
input2.value = 'world';
input2.dispatchEvent(inputEvent);
input2.dispatchEvent(input_event);
assert.equal(input2.value, 'world');
assert.equal(component.array[1].value, 'world');
}

@ -1,6 +1,6 @@
const VALUES = Array.from('abcdefghijklmnopqrstuvwxyz');
function toObjects(array) {
function to_objects(array) {
return array.split('').map((x) => ({ id: x }));
}
@ -17,7 +17,7 @@ function permute() {
export default {
get props() {
return { values: toObjects('abc') };
return { values: to_objects('abc') };
},
html: '(a)(b)(c)',
@ -29,7 +29,7 @@ export default {
.split('')
.map((x) => `(${x})`)
.join('');
component.values = toObjects(sequence);
component.values = to_objects(sequence);
assert.htmlEqual(
target.innerHTML,
expected,

@ -6,8 +6,8 @@ export default {
async test({ assert, target, window }) {
const button = target.querySelector('button');
const clickEvent = new window.MouseEvent('click');
await button.dispatchEvent(clickEvent);
const click_event = new window.MouseEvent('click');
await button.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,

@ -9,10 +9,10 @@ export default {
<button>Test</button>
`,
async test({ assert, target, window }) {
let [incrementBtn, ...buttons] = target.querySelectorAll('button');
let [increment_btn, ...buttons] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click');
await buttons[0].dispatchEvent(clickEvent);
const click_event = new window.MouseEvent('click');
await buttons[0].dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -27,7 +27,7 @@ export default {
`
);
await buttons[0].dispatchEvent(clickEvent);
await buttons[0].dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -42,8 +42,8 @@ export default {
`
);
await buttons[2].dispatchEvent(clickEvent);
await buttons[2].dispatchEvent(clickEvent);
await buttons[2].dispatchEvent(click_event);
await buttons[2].dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -58,7 +58,7 @@ export default {
`
);
await incrementBtn.dispatchEvent(clickEvent);
await increment_btn.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -75,9 +75,9 @@ export default {
`
);
[incrementBtn, ...buttons] = target.querySelectorAll('button');
[increment_btn, ...buttons] = target.querySelectorAll('button');
await buttons[3].dispatchEvent(clickEvent);
await buttons[3].dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,

@ -9,7 +9,7 @@ export default {
`,
async test({ assert, target, window }) {
const [updateButton1, updateButton2, button] = target.querySelectorAll('button');
const [update_button1, update_button2, button] = target.querySelectorAll('button');
const event = new window.MouseEvent('click');
let err = '';
@ -32,7 +32,7 @@ export default {
`
);
await updateButton1.dispatchEvent(event);
await update_button1.dispatchEvent(event);
await button.dispatchEvent(event);
assert.htmlEqual(
target.innerHTML,
@ -46,7 +46,7 @@ export default {
`
);
await updateButton2.dispatchEvent(event);
await update_button2.dispatchEvent(event);
await button.dispatchEvent(event);
assert.htmlEqual(
target.innerHTML,

@ -4,7 +4,7 @@ export default {
<button>invalid</button>`,
async test({ assert, target, window }) {
const [buttonUndef, buttonNull, buttonInvalid] = target.querySelectorAll('button');
const [button_undef, button_null, button_invalid] = target.querySelectorAll('button');
const event = new window.MouseEvent('click');
let err = '';
@ -14,13 +14,13 @@ export default {
});
// All three should not throw if proper checking is done in runtime code
await buttonUndef.dispatchEvent(event);
await button_undef.dispatchEvent(event);
assert.equal(err, '', err);
await buttonNull.dispatchEvent(event);
await button_null.dispatchEvent(event);
assert.equal(err, '', err);
await buttonInvalid.dispatchEvent(event);
await button_invalid.dispatchEvent(event);
assert.equal(err, '', err);
}
};

@ -9,7 +9,7 @@ export default {
`,
async test({ assert, target, window }) {
const [updateButton1, updateButton2, button] = target.querySelectorAll('button');
const [update_button1, update_button2, button] = target.querySelectorAll('button');
const event = new window.MouseEvent('click');
let err = '';
@ -32,7 +32,7 @@ export default {
`
);
await updateButton1.dispatchEvent(event);
await update_button1.dispatchEvent(event);
await button.dispatchEvent(event);
assert.htmlEqual(
target.innerHTML,
@ -46,7 +46,7 @@ export default {
`
);
await updateButton2.dispatchEvent(event);
await update_button2.dispatchEvent(event);
await button.dispatchEvent(event);
assert.htmlEqual(
target.innerHTML,

@ -10,21 +10,21 @@ export default {
assert.equal(component.updated, 4);
const [item1, item2] = target.childNodes;
const [item1Btn1, item1Btn2] = item1.querySelectorAll('button');
const [item2Btn1, item2Btn2] = item2.querySelectorAll('button');
const [item1_btn1, item1_btn2] = item1.querySelectorAll('button');
const [item2_btn1, item2_btn2] = item2.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click');
const click_event = new window.MouseEvent('click');
await item1Btn1.dispatchEvent(clickEvent);
await item1_btn1.dispatchEvent(click_event);
assert.equal(component.getNormalCount(), 1);
await item1Btn2.dispatchEvent(clickEvent);
await item1_btn2.dispatchEvent(click_event);
assert.equal(component.getModifierCount(), 1);
await item2Btn1.dispatchEvent(clickEvent);
await item2_btn1.dispatchEvent(click_event);
assert.equal(component.getNormalCount(), 2);
await item2Btn2.dispatchEvent(clickEvent);
await item2_btn2.dispatchEvent(click_event);
assert.equal(component.getModifierCount(), 2);
}
};

@ -11,11 +11,11 @@ export default {
`,
test({ assert, component }) {
const visibleThings = component.visibleThings;
assert.deepEqual(visibleThings, ['first thing', 'second thing']);
const visible_things = component.visibleThings;
assert.deepEqual(visible_things, ['first thing', 'second thing']);
const snapshots = component.snapshots;
assert.deepEqual(snapshots, [visibleThings]);
assert.deepEqual(snapshots, [visible_things]);
// TODO minimise the number of recomputations during oncreate
// assert.equal(counter.count, 1);

@ -7,10 +7,14 @@ export default {
},
async test({ assert, target }) {
const firstSpanList = target.children[0];
assert.htmlEqualWithOptions(firstSpanList.innerHTML, expected, { withoutNormalizeHtml: true });
const first_span_list = target.children[0];
assert.htmlEqualWithOptions(first_span_list.innerHTML, expected, {
withoutNormalizeHtml: true
});
const secondSpanList = target.children[1];
assert.htmlEqualWithOptions(secondSpanList.innerHTML, expected, { withoutNormalizeHtml: true });
const second_span_list = target.children[1];
assert.htmlEqualWithOptions(second_span_list.innerHTML, expected, {
withoutNormalizeHtml: true
});
}
};

@ -2,9 +2,9 @@ export default {
async test({ assert, target, window }) {
const [btn1, btn2] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click');
const click_event = new window.MouseEvent('click');
await btn2.dispatchEvent(clickEvent);
await btn2.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
`
@ -17,7 +17,7 @@ export default {
`
);
await btn1.dispatchEvent(clickEvent);
await btn1.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
`
@ -30,7 +30,7 @@ export default {
`
);
await btn2.dispatchEvent(clickEvent);
await btn2.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
`
@ -43,7 +43,7 @@ export default {
`
);
await btn1.dispatchEvent(clickEvent);
await btn1.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
`

@ -4,14 +4,14 @@ export default {
<div>&nbsp;hello&nbsp; &nbsp;hello</div>`,
test({ assert, target }) {
const divList = target.querySelectorAll('div');
assert.equal(divList[0].textContent.charCodeAt(0), 160);
assert.equal(divList[1].textContent.charCodeAt(0), 160);
assert.equal(divList[1].textContent.charCodeAt(6), 160);
assert.equal(divList[1].textContent.charCodeAt(7), 160);
assert.equal(divList[2].textContent.charCodeAt(0), 160);
assert.equal(divList[2].textContent.charCodeAt(6), 160);
assert.equal(divList[2].textContent.charCodeAt(7), 32); //normal space
assert.equal(divList[2].textContent.charCodeAt(8), 160);
const div_list = target.querySelectorAll('div');
assert.equal(div_list[0].textContent.charCodeAt(0), 160);
assert.equal(div_list[1].textContent.charCodeAt(0), 160);
assert.equal(div_list[1].textContent.charCodeAt(6), 160);
assert.equal(div_list[1].textContent.charCodeAt(7), 160);
assert.equal(div_list[2].textContent.charCodeAt(0), 160);
assert.equal(div_list[2].textContent.charCodeAt(6), 160);
assert.equal(div_list[2].textContent.charCodeAt(7), 32); //normal space
assert.equal(div_list[2].textContent.charCodeAt(8), 160);
}
};

@ -14,7 +14,7 @@ export default {
// it's okay not to remove the node during hydration
// will not be seen by user anyway
removeNoScript(target);
remove_no_script(target);
assert.htmlEqual(
target.innerHTML,
@ -26,7 +26,7 @@ export default {
}
};
function removeNoScript(target) {
function remove_no_script(target) {
target.querySelectorAll('noscript').forEach((elem) => {
elem.parentNode.removeChild(elem);
});

@ -6,9 +6,9 @@ export default {
async test({ assert, target, window }) {
const btn = target.querySelector('button');
const clickEvent = new window.MouseEvent('click');
const click_event = new window.MouseEvent('click');
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,

@ -6,9 +6,9 @@ export default {
`,
async test({ assert, target, window }) {
const btn = target.querySelector('button');
const clickEvent = new window.MouseEvent('click');
const click_event = new window.MouseEvent('click');
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -19,7 +19,7 @@ export default {
`
);
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,

@ -1,7 +1,7 @@
export default {
async test({ assert, target, window }) {
const btn = target.querySelector('button');
const clickEvent = new window.MouseEvent('click');
const click_event = new window.MouseEvent('click');
assert.equal(
window.document.head.innerHTML.includes(
@ -10,7 +10,7 @@ export default {
true
);
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.equal(
window.document.head.innerHTML.includes(
@ -19,7 +19,7 @@ export default {
true
);
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.equal(
window.document.head.innerHTML.includes(

@ -6,9 +6,9 @@ export default {
`,
async test({ assert, target, window }) {
const btn = target.querySelector('button');
const clickEvent = new window.MouseEvent('click');
const click_event = new window.MouseEvent('click');
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -19,7 +19,7 @@ export default {
`
);
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,

@ -13,9 +13,9 @@ export default {
},
async test({ assert, target, window }) {
const btn = target.querySelector('button');
const clickEvent = new window.MouseEvent('click');
const click_event = new window.MouseEvent('click');
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
@ -28,7 +28,7 @@ export default {
`
);
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,

@ -11,8 +11,8 @@ export default {
// and we determine if svelte does not set the `input.value` again by
// spying on the setter of `input.value`
const spy1 = spyOnValueSetter(input1, input1.value);
const spy2 = spyOnValueSetter(input2, input2.value);
const spy1 = spy_on_value_setter(input1, input1.value);
const spy2 = spy_on_value_setter(input2, input2.value);
const event = new window.Event('input');
@ -38,25 +38,25 @@ export default {
}
};
function spyOnValueSetter(input, initialValue) {
let value = initialValue;
let isSet = false;
function spy_on_value_setter(input, initial_value) {
let value = initial_value;
let is_set = false;
Object.defineProperty(input, 'value', {
get() {
return value;
},
set(_value) {
value = _value;
isSet = true;
is_set = true;
}
});
return {
isSetCalled() {
return isSet;
return is_set;
},
reset() {
isSet = false;
is_set = false;
}
};
}

@ -1,6 +1,6 @@
export function omit(obj, ...keysToOmit) {
export function omit(obj, ...keys_to_omit) {
return Object.keys(obj).reduce((acc, key) => {
if (keysToOmit.indexOf(key) === -1) acc[key] = obj[key];
if (keys_to_omit.indexOf(key) === -1) acc[key] = obj[key];
return acc;
}, {});
}

@ -9,9 +9,9 @@ export default {
const input = target.querySelector('input');
input.value = 'foo';
const inputEvent = new window.InputEvent('input');
const input_event = new window.InputEvent('input');
await input.dispatchEvent(inputEvent);
await input.dispatchEvent(input_event);
assert.htmlEqual(
target.innerHTML,

@ -16,11 +16,11 @@ export default {
const input = target.querySelector('input');
const button = target.querySelector('button');
const inputEvent = new window.InputEvent('input');
const clickEvent = new window.MouseEvent('click');
const input_event = new window.InputEvent('input');
const click_event = new window.MouseEvent('click');
input.value = 'foo';
await input.dispatchEvent(inputEvent);
await input.dispatchEvent(input_event);
assert.htmlEqual(
target.innerHTML,
@ -32,7 +32,7 @@ export default {
`
);
await button.dispatchEvent(clickEvent);
await button.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
`
@ -44,7 +44,7 @@ export default {
);
input.value = 'bar';
await input.dispatchEvent(inputEvent);
await input.dispatchEvent(input_event);
assert.htmlEqual(
target.innerHTML,

@ -16,11 +16,11 @@ export default {
const input = target.querySelector('input');
const button = target.querySelector('button');
const inputEvent = new window.InputEvent('input');
const clickEvent = new window.MouseEvent('click');
const input_event = new window.InputEvent('input');
const click_event = new window.MouseEvent('click');
input.value = 'foo';
await input.dispatchEvent(inputEvent);
await input.dispatchEvent(input_event);
assert.htmlEqual(
target.innerHTML,
@ -32,7 +32,7 @@ export default {
`
);
await button.dispatchEvent(clickEvent);
await button.dispatchEvent(click_event);
assert.htmlEqual(
target.innerHTML,
`
@ -44,7 +44,7 @@ export default {
);
input.value = 'bar';
await input.dispatchEvent(inputEvent);
await input.dispatchEvent(input_event);
assert.htmlEqual(
target.innerHTML,

@ -1,11 +1,11 @@
let unsubscribeCalled = false;
let unsubscribe_called = false;
const fakeStore = (val) => ({
const fake_store = (val) => ({
subscribe: (cb) => {
cb(val);
return {
unsubscribe: () => {
unsubscribeCalled = true;
unsubscribe_called = true;
}
};
}
@ -13,17 +13,17 @@ const fakeStore = (val) => ({
export default {
get props() {
return { foo: fakeStore(1) };
return { foo: fake_store(1) };
},
html: `
<h1>1</h1>
`,
async test({ assert, component, target }) {
component.foo = fakeStore(5);
component.foo = fake_store(5);
assert.htmlEqual(target.innerHTML, '<h1>5</h1>');
assert.ok(unsubscribeCalled);
assert.ok(unsubscribe_called);
}
};

@ -8,8 +8,8 @@ export default {
`,
test({ assert, target }) {
const foreignObject = target.querySelector('foreignObject');
assert.equal(foreignObject.namespaceURI, 'http://www.w3.org/2000/svg');
const foreign_object = target.querySelector('foreignObject');
assert.equal(foreign_object.namespaceURI, 'http://www.w3.org/2000/svg');
const p = target.querySelector('p');
assert.equal(p.namespaceURI, 'http://www.w3.org/1999/xhtml');

@ -18,50 +18,53 @@ multiple leading newlines</textarea> <div id="div-with-textarea-with-multiple-le
multiple leading newlines</textarea></div>`,
test({ assert, target }) {
// Test for <textarea> tag
const elementTextarea = target.querySelector('#textarea');
const element_textarea = target.querySelector('#textarea');
// Test for <textarea> tag in non <textarea> tag
const elementDivWithTextarea = target.querySelector('#div-with-textarea');
const element_div_with_textarea = target.querySelector('#div-with-textarea');
// Test for <textarea> tag with leading newline
const elementTextareaWithLeadingNewline = target.querySelector(
const element_textarea_with_leading_newline = target.querySelector(
'#textarea-with-leading-newline'
);
const elementTextareaWithoutLeadingNewline = target.querySelector(
const element_textarea_without_leading_newline = target.querySelector(
'#textarea-without-leading-newline'
);
const elementTextareaWithMultipleLeadingNewline = target.querySelector(
const element_textarea_with_multiple_leading_newline = target.querySelector(
'#textarea-with-multiple-leading-newlines'
);
const elementDivWithTextareaWithMultipleLeadingNewline = target.querySelector(
const element_div_with_textarea_with_multiple_leading_newline = target.querySelector(
'#div-with-textarea-with-multiple-leading-newlines'
);
assert.equal(
elementTextarea.value,
element_textarea.value,
` A
B
`
);
assert.equal(
elementDivWithTextarea.children[0].value,
element_div_with_textarea.children[0].value,
` A
B
`
);
assert.equal(elementTextareaWithLeadingNewline.children[0].value, 'leading newline');
assert.equal(element_textarea_with_leading_newline.children[0].value, 'leading newline');
assert.equal(
elementTextareaWithLeadingNewline.children[1].value,
element_textarea_with_leading_newline.children[1].value,
' leading newline and spaces'
);
assert.equal(elementTextareaWithLeadingNewline.children[2].value, '\nleading newlines');
assert.equal(elementTextareaWithoutLeadingNewline.children[0].value, 'without spaces');
assert.equal(elementTextareaWithoutLeadingNewline.children[1].value, ' with spaces ');
assert.equal(element_textarea_with_leading_newline.children[2].value, '\nleading newlines');
assert.equal(element_textarea_without_leading_newline.children[0].value, 'without spaces');
assert.equal(element_textarea_without_leading_newline.children[1].value, ' with spaces ');
assert.equal(
elementTextareaWithoutLeadingNewline.children[2].value,
element_textarea_without_leading_newline.children[2].value,
' \nnewline after leading space'
);
assert.equal(elementTextareaWithMultipleLeadingNewline.value, '\n\nmultiple leading newlines');
assert.equal(
elementDivWithTextareaWithMultipleLeadingNewline.children[0].value,
element_textarea_with_multiple_leading_newline.value,
'\n\nmultiple leading newlines'
);
assert.equal(
element_div_with_textarea_with_multiple_leading_newline.children[0].value,
'\n\nmultiple leading newlines'
);
}

@ -1,9 +1,9 @@
export default {
async test({ assert, target, window }) {
const [, btn] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click');
const click_event = new window.MouseEvent('click');
await btn.dispatchEvent(clickEvent);
await btn.dispatchEvent(click_event);
assert.equal(btn.x, 1);
}

@ -1,6 +1,6 @@
import { vi } from 'vitest';
let original_scrollTo;
let original_scroll_to;
export default {
before_test() {
vi.useFakeTimers();
@ -17,7 +17,7 @@ export default {
writable: true
}
});
original_scrollTo = window.scrollTo;
original_scroll_to = window.scrollTo;
window.scrollTo = (x, y) => {
window.pageXOffset = x;
window.pageYOffset = y;
@ -26,7 +26,7 @@ export default {
after_test() {
vi.useRealTimers();
window.scrollTo = original_scrollTo;
window.scrollTo = original_scroll_to;
},
async test({ assert, component, window }) {

@ -0,0 +1,2 @@
<link rel="canonical" href="/test">
<meta name="description" content="test">

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

Loading…
Cancel
Save