Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
class:name={ true } in combination with spread arguments is deleted on update
**Describe the bug**
`class:name={ true }` in combination with spread arguments is deleted on spread arguments update

**To Reproduce**
REPL https://svelte.dev/repl/ff608fce63184816a57bbb5ae0ea7bd3?version=3.8.1

**Additional context**
```html
<h1 class="cls" class:red={ true } { ...{ style } }>
```
update function is
```js
p(changed, ctx) {
	set_attributes(h1, get_spread_update(h1_levels, [
		{ class: "cls svelte-kwtnbw" },
		(changed.style) && { style: ctx.style }
	]));
},
```

Because `get_spread_update` change `class` attribute and there is no toggle class call for `red` class, `red` class is deleted. Good solution is to compile truthy values of `class:name={truthy}` like static.


Discussion:

Resolving patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 * Fix `{#each}` context not shadowing outer scope when using `bind:` ([#1565](https://github.com/sveltejs/svelte/issues/1565))
 * Fix edge cases in matching selectors against elements ([#1710](https://github.com/sveltejs/svelte/issues/1710))
 * Allow exiting a reactive block early with `break $` ([#2828](https://github.com/sveltejs/svelte/issues/2828))
+* Don't lose `class:` directive classes on an element with `{...spread}` attributes when updating ([#3421](https://github.com/sveltejs/svelte/issues/3421))
 * Check attributes have changed before setting them to avoid image flicker ([#3579](https://github.com/sveltejs/svelte/pull/3579))
 * Fix generating malformed code for `{@debug}` tags with no dependencies ([#3588](https://github.com/sveltejs/svelte/issue/3588))
 * Fix generated code in specific case involving compound ifs and child components ([#3595](https://github.com/sveltejs/svelte/issue/3595))
diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts
--- a/src/compiler/compile/render_dom/wrappers/Element/index.ts
+++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts
@@ -809,6 +809,7 @@ export default class ElementWrapper extends Wrapper {
 	}
 
 	add_classes(block: Block) {
+		const has_spread = this.node.attributes.some(attr => attr.is_spread);
 		this.node.classes.forEach(class_directive => {
 			const { expression, name } = class_directive;
 			let snippet;
@@ -824,7 +825,9 @@ export default class ElementWrapper extends Wrapper {
 
 			block.chunks.hydrate.push(updater);
 
-			if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
+			if (has_spread) {
+				block.chunks.update.push(updater);
+			} else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
 				const all_dependencies = this.class_dependencies.concat(...dependencies);
 				const condition = changed(all_dependencies);
 


Test patch:
diff --git a/test/runtime/samples/spread-element-class/_config.js b/test/runtime/samples/spread-element-class/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/spread-element-class/_config.js
@@ -0,0 +1,7 @@
+export default {
+	html: `<div class='foo bar'>hello</div>`,
+	test({ assert, component, target }) {
+		component.blah = 'goodbye';
+		assert.htmlEqual(target.innerHTML, `<div class='foo bar'>goodbye</div>`);
+	}
+};
diff --git a/test/runtime/samples/spread-element-class/main.svelte b/test/runtime/samples/spread-element-class/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/spread-element-class/main.svelte
@@ -0,0 +1,5 @@
+<script>
+	export let blah = 'hello';
+</script>
+
+<div class='foo' class:bar={true} {...{}}>{blah}</div>