Skip to content

Commit 36a437c

Browse files
authored
fix: compare array contents for equality mismatch detections, not the arrays themselves (#14738)
1 parent d7e4bd2 commit 36a437c

File tree

4 files changed

+84
-18
lines changed

4 files changed

+84
-18
lines changed

.changeset/tasty-deers-agree.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: compare array contents for equality mismatch detections, not the arrays themselves

packages/svelte/src/internal/client/dev/equality.js

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ export function init_array_prototype_warnings() {
1717
const index = indexOf.call(this, item, from_index);
1818

1919
if (index === -1) {
20-
const test = indexOf.call(get_proxied_value(this), get_proxied_value(item), from_index);
21-
22-
if (test !== -1) {
23-
w.state_proxy_equality_mismatch('array.indexOf(...)');
20+
for (let i = from_index ?? 0; i < this.length; i += 1) {
21+
if (get_proxied_value(this[i]) === item) {
22+
w.state_proxy_equality_mismatch('array.indexOf(...)');
23+
break;
24+
}
2425
}
2526
}
2627

@@ -33,16 +34,11 @@ export function init_array_prototype_warnings() {
3334
const index = lastIndexOf.call(this, item, from_index ?? this.length - 1);
3435

3536
if (index === -1) {
36-
// we need to specify this.length - 1 because it's probably using something like
37-
// `arguments` inside so passing undefined is different from not passing anything
38-
const test = lastIndexOf.call(
39-
get_proxied_value(this),
40-
get_proxied_value(item),
41-
from_index ?? this.length - 1
42-
);
43-
44-
if (test !== -1) {
45-
w.state_proxy_equality_mismatch('array.lastIndexOf(...)');
37+
for (let i = 0; i <= (from_index ?? this.length - 1); i += 1) {
38+
if (get_proxied_value(this[i]) === item) {
39+
w.state_proxy_equality_mismatch('array.lastIndexOf(...)');
40+
break;
41+
}
4642
}
4743
}
4844

@@ -53,10 +49,11 @@ export function init_array_prototype_warnings() {
5349
const has = includes.call(this, item, from_index);
5450

5551
if (!has) {
56-
const test = includes.call(get_proxied_value(this), get_proxied_value(item), from_index);
57-
58-
if (test) {
59-
w.state_proxy_equality_mismatch('array.includes(...)');
52+
for (let i = 0; i < this.length; i += 1) {
53+
if (get_proxied_value(this[i]) === item) {
54+
w.state_proxy_equality_mismatch('array.includes(...)');
55+
break;
56+
}
6057
}
6158
}
6259

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
compileOptions: {
6+
dev: true
7+
},
8+
9+
async test({ assert, target, warnings }) {
10+
const [btn1, btn2, btn3, btn4, btn5, btn6, clear] = target.querySelectorAll('button');
11+
12+
flushSync(() => {
13+
btn1.click();
14+
btn2.click();
15+
btn3.click();
16+
btn4.click();
17+
btn5.click();
18+
btn6.click();
19+
});
20+
21+
assert.deepEqual(warnings, [
22+
'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.includes(...)` will produce unexpected results',
23+
'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.indexOf(...)` will produce unexpected results',
24+
'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.lastIndexOf(...)` will produce unexpected results'
25+
]);
26+
27+
flushSync(() => clear.click());
28+
warnings.length = 0;
29+
30+
flushSync(() => {
31+
btn1.click();
32+
btn2.click();
33+
btn3.click();
34+
btn4.click();
35+
btn5.click();
36+
btn6.click();
37+
});
38+
39+
assert.deepEqual(warnings, []);
40+
}
41+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script>
2+
let primitive = 'foo';
3+
let object = {};
4+
5+
let array = $state([primitive, object]);
6+
</script>
7+
8+
<button onclick={() => array.includes(primitive)}>array.includes(primitive)</button>
9+
<button onclick={() => array.includes(object)}>array.includes(object)</button>
10+
11+
<hr />
12+
13+
<button onclick={() => array.indexOf(primitive)}>array.indexOf(primitive)</button>
14+
<button onclick={() => array.indexOf(object)}>array.indexOf(object)</button>
15+
16+
<hr />
17+
18+
<button onclick={() => array.lastIndexOf(primitive)}>array.lastIndexOf(primitive)</button>
19+
<button onclick={() => array.lastIndexOf(object)}>array.lastIndexOf(object)</button>
20+
21+
<hr />
22+
23+
<button onclick={() => (array.length = 0)}>clear</button>

0 commit comments

Comments
 (0)