Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/utilities/watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export function watch(get_value, callback, options = null) {
}

if (initialized || (options?.immediate ?? true)) {
// Keep a single pending callback: it reports the latest value anyway,
// and the disposer cancels only the timer it knows about.
clearTimeout(timer_id);

// Prevent the watcher from detecting its own dependencies.
timer_id = setTimeout(() => {
callback(new_value, old_value);
Expand Down
70 changes: 70 additions & 0 deletions tests/utilities/watch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, expect, test } from "vitest";
import { watch } from "@/utilities/watch";
import Alpine from "alpinejs";

// watch takes Alpine from the global scope
globalThis.Alpine = Alpine;

// Lets Alpine run the scheduled effects, but not the timers the watcher sets
const flush_effects = () => new Promise(queueMicrotask);

// Lets the timers set so far fire; the effects must be flushed first,
// otherwise the timers they set would fire after this one
const flush_timers = () => new Promise(resolve => setTimeout(resolve));

describe("watch", () => {
test("reports changes made before the callback once", async () => {
const data = Alpine.reactive({ value: 0 });
const tracing = [];

watch(
() => data.value,
(value, old_value) => tracing.push([value, old_value]),
{ immediate: false });

data.value = 1;
await flush_effects();

data.value = 2;
await flush_effects();
await flush_timers();

expect(tracing).toEqual([[2, 0]]);
});

test("immediate: reports a change made before the first callback once", async () => {
const data = Alpine.reactive({ value: 0 });
const tracing = [];

watch(
() => data.value,
(value, old_value) => tracing.push([value, old_value]));

data.value = 1;
await flush_effects();
await flush_timers();

expect(tracing).toEqual([[1, 0]]);
});

test("no callback after disposal", async () => {
const data = Alpine.reactive({ value: 0 });
const tracing = [];

const dispose = watch(
() => data.value,
(value, old_value) => tracing.push([value, old_value]),
{ immediate: false });

data.value = 1;
await flush_effects();

data.value = 2;
await flush_effects();

dispose();
await flush_timers();

expect(tracing).toEqual([]);
});
});
3 changes: 3 additions & 0 deletions vitest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
} from "url";

export default defineConfig({
define: {
__DEV__: true
},
test: {
globals: true,
environment: "jsdom",
Expand Down
Loading