diff options
author | 2023-05-14 14:18:56 -0300 | |
---|---|---|
committer | 2023-05-14 10:18:56 -0700 | |
commit | bf9e40d5b49f1ed16ea9abed4e231456dcda99c0 (patch) | |
tree | a803971285c5f4329bbee4ef025151baaf91c817 | |
parent | 2a66229b0f9aabf96891afe3063e07ec877527ca (diff) | |
download | bun-bf9e40d5b49f1ed16ea9abed4e231456dcda99c0.tar.gz bun-bf9e40d5b49f1ed16ea9abed4e231456dcda99c0.tar.zst bun-bf9e40d5b49f1ed16ea9abed4e231456dcda99c0.zip |
feat(Timer.refresh) add refresh support on Timer (#2874)
* add refresh support on Timer
* fix this return
* add refresh setTimeout tests
* fix tests and add setInterval test
* use setCached for arguments and callback
---------
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
-rw-r--r-- | src/bun.js/api/bun.zig | 155 | ||||
-rw-r--r-- | src/bun.js/bindings/JSSink.cpp | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/JSSink.h | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/JSSinkLookupTable.h | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 79 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.h | 7 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes.zig | 47 | ||||
-rw-r--r-- | src/bun.js/node/node.classes.ts | 5 | ||||
-rw-r--r-- | test/js/web/timers/setInterval.test.js | 14 | ||||
-rw-r--r-- | test/js/web/timers/setTimeout.test.js | 59 |
10 files changed, 345 insertions, 27 deletions
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 5b965e969..da4e2945b 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -2990,13 +2990,123 @@ pub const Timer = struct { id: i32 = -1, kind: Timeout.Kind = .setTimeout, ref_count: u16 = 1, + interval: i32 = 0, + // we do not allow the timer to be refreshed after we call clearInterval/clearTimeout + has_cleaned_up: bool = false, pub usingnamespace JSC.Codegen.JSTimeout; + pub fn init(globalThis: *JSGlobalObject, id: i32, kind: Timeout.Kind, interval: i32, callback: JSValue, arguments: JSValue) JSValue { + var timer = globalThis.allocator().create(TimerObject) catch unreachable; + timer.* = .{ + .id = id, + .kind = kind, + .interval = interval, + }; + var timer_js = timer.toJS(globalThis); + timer_js.ensureStillAlive(); + TimerObject.argumentsSetCached(timer_js, globalThis, arguments); + TimerObject.callbackSetCached(timer_js, globalThis, callback); + timer_js.ensureStillAlive(); + return timer_js; + } + pub fn doRef(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { if (this.ref_count > 0) this.ref_count +|= 1; + return JSValue.jsUndefined(); + } + + pub fn doRefresh(this: *TimerObject, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { + // TODO: this is not the optimal way to do this but it works, we should revisit this and optimize it + // like truly resetting the timer instead of removing and re-adding when possible + const this_value = callframe.this(); + + // setImmediate does not support refreshing and we do not support refreshing after cleanup + if (this.has_cleaned_up or this.id == -1 or this.kind == .setImmediate) { + return JSValue.jsUndefined(); + } + const vm = globalThis.bunVM(); + var map = vm.timer.maps.get(this.kind); + + // reschedule the event + if (TimerObject.callbackGetCached(this_value)) |callback| { + callback.ensureStillAlive(); + + const id: Timeout.ID = .{ + .id = this.id, + .kind = this.kind, + }; + + if (this.kind == .setTimeout and this.interval == 0) { + var cb: CallbackJob = .{ + .callback = JSC.Strong.create(callback, globalThis), + .globalThis = globalThis, + .id = this.id, + .kind = this.kind, + }; + + if (TimerObject.argumentsGetCached(this_value)) |arguments| { + arguments.ensureStillAlive(); + cb.arguments = JSC.Strong.create(arguments, globalThis); + } + + var job = vm.allocator.create(CallbackJob) catch @panic( + "Out of memory while allocating Timeout", + ); + + job.* = cb; + job.task = CallbackJob.Task.init(job); + job.ref.ref(vm); + + // cancel the current event if exists before re-adding it + if (map.fetchSwapRemove(this.id)) |timer| { + if (timer.value != null) { + var value = timer.value.?; + value.deinit(); + } + } + + vm.enqueueTask(JSC.Task.init(&job.task)); + + map.put(vm.allocator, this.id, null) catch unreachable; + return this_value; + } + + var timeout = Timeout{ + .callback = JSC.Strong.create(callback, globalThis), + .globalThis = globalThis, + .timer = uws.Timer.create( + vm.uws_event_loop.?, + id, + ), + }; + + if (TimerObject.argumentsGetCached(this_value)) |arguments| { + arguments.ensureStillAlive(); + timeout.arguments = JSC.Strong.create(arguments, globalThis); + } + + timeout.poll_ref.ref(vm); + + // cancel the current event if exists before re-adding it + if (map.fetchSwapRemove(this.id)) |timer| { + if (timer.value != null) { + var value = timer.value.?; + value.deinit(); + } + } + + map.put(vm.allocator, this.id, timeout) catch unreachable; + timeout.timer.set( + id, + Timeout.run, + this.interval, + @as(i32, @boolToInt(this.kind == .setInterval)) * this.interval, + ); + return this_value; + } return JSValue.jsUndefined(); } @@ -3022,6 +3132,10 @@ pub const Timer = struct { return JSValue.jsNumber(this.id); } + pub fn markHasClear(this: *TimerObject) void { + this.has_cleaned_up = true; + } + pub fn finalize(this: *TimerObject) callconv(.C) void { bun.default_allocator.destroy(this); } @@ -3143,20 +3257,13 @@ pub const Timer = struct { id: i32, globalThis: *JSGlobalObject, callback: JSValue, - countdown: JSValue, + interval: i32, arguments_array_or_zero: JSValue, repeat: bool, ) !void { JSC.markBinding(@src()); var vm = globalThis.bunVM(); - // We don't deal with nesting levels directly - // but we do set the minimum timeout to be 1ms for repeating timers - const interval: i32 = @max( - countdown.coerce(i32, globalThis), - if (repeat) @as(i32, 1) else 0, - ); - const kind: Timeout.Kind = if (repeat) .setInterval else .setTimeout; var map = vm.timer.maps.get(kind); @@ -3228,16 +3335,15 @@ pub const Timer = struct { const id = globalThis.bunVM().timer.last_id; globalThis.bunVM().timer.last_id +%= 1; - Timer.set(id, globalThis, callback, countdown, arguments, false) catch - return JSValue.jsUndefined(); + const interval: i32 = @max( + countdown.coerce(i32, globalThis), + 0, + ); - var timer = globalThis.allocator().create(TimerObject) catch unreachable; - timer.* = .{ - .id = id, - .kind = .setTimeout, - }; + Timer.set(id, globalThis, callback, interval, arguments, false) catch + return JSValue.jsUndefined(); - return timer.toJS(globalThis); + return TimerObject.init(globalThis, id, .setTimeout, interval, callback, arguments); } pub fn setInterval( globalThis: *JSGlobalObject, @@ -3249,16 +3355,16 @@ pub const Timer = struct { const id = globalThis.bunVM().timer.last_id; globalThis.bunVM().timer.last_id +%= 1; - Timer.set(id, globalThis, callback, countdown, arguments, true) catch + // We don't deal with nesting levels directly + // but we do set the minimum timeout to be 1ms for repeating timers + const interval: i32 = @max( + countdown.coerce(i32, globalThis), + 1, + ); + Timer.set(id, globalThis, callback, interval, arguments, true) catch return JSValue.jsUndefined(); - var timer = globalThis.allocator().create(TimerObject) catch unreachable; - timer.* = .{ - .id = id, - .kind = .setInterval, - }; - - return timer.toJS(globalThis); + return TimerObject.init(globalThis, id, .setInterval, interval, callback, arguments); } pub fn clearTimer(timer_id_value: JSValue, globalThis: *JSGlobalObject, repeats: bool) void { @@ -3275,6 +3381,7 @@ pub const Timer = struct { } if (TimerObject.fromJS(timer_id_value)) |timer_obj| { + timer_obj.markHasClear(); break :brk timer_obj.id; } diff --git a/src/bun.js/bindings/JSSink.cpp b/src/bun.js/bindings/JSSink.cpp index 31e648b8a..2fb03963e 100644 --- a/src/bun.js/bindings/JSSink.cpp +++ b/src/bun.js/bindings/JSSink.cpp @@ -1,6 +1,6 @@ // AUTO-GENERATED FILE. DO NOT EDIT. -// Generated by 'make generate-sink' at 2023-04-27T21:24:10.276Z +// Generated by 'make generate-sink' at 2023-05-14T13:28:43.914Z // To regenerate this file, run: // // make generate-sink diff --git a/src/bun.js/bindings/JSSink.h b/src/bun.js/bindings/JSSink.h index 29c5b2a09..9726f68e9 100644 --- a/src/bun.js/bindings/JSSink.h +++ b/src/bun.js/bindings/JSSink.h @@ -1,6 +1,6 @@ // AUTO-GENERATED FILE. DO NOT EDIT. -// Generated by 'make generate-sink' at 2023-04-27T21:24:10.274Z +// Generated by 'make generate-sink' at 2023-05-14T13:28:43.910Z // #pragma once diff --git a/src/bun.js/bindings/JSSinkLookupTable.h b/src/bun.js/bindings/JSSinkLookupTable.h index 2f92be340..e4ed81629 100644 --- a/src/bun.js/bindings/JSSinkLookupTable.h +++ b/src/bun.js/bindings/JSSinkLookupTable.h @@ -1,4 +1,4 @@ -// Automatically generated from src/bun.js/bindings/JSSink.cpp using /home/will/dev/bun-test-universe/src/bun.js/WebKit/Source/JavaScriptCore/create_hash_table. DO NOT EDIT! +// Automatically generated from src/bun.js/bindings/JSSink.cpp using /home/cirospaciari/Repos/bun/src/bun.js/WebKit/Source/JavaScriptCore/create_hash_table. DO NOT EDIT! diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index 137d94fe2..a8ec4c6b5 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -13556,6 +13556,9 @@ JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__hasRefCallback); extern "C" EncodedJSValue TimeoutPrototype__doRef(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__refCallback); +extern "C" EncodedJSValue TimeoutPrototype__doRefresh(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__refreshCallback); + extern "C" EncodedJSValue TimeoutPrototype__doUnref(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__unrefCallback); @@ -13564,6 +13567,7 @@ STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTimeoutPrototype, JSTimeoutPrototype::Base static const HashTableValue JSTimeoutPrototypeTableValues[] = { { "hasRef"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TimeoutPrototype__hasRefCallback, 0 } }, { "ref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TimeoutPrototype__refCallback, 0 } }, + { "refresh"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TimeoutPrototype__refreshCallback, 0 } }, { "unref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TimeoutPrototype__unrefCallback, 0 } } }; @@ -13617,6 +13621,22 @@ JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__refCallback, (JSGlobalObject * lexica return TimeoutPrototype__doRef(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__refreshCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSTimeout* thisObject = jsDynamicCast<JSTimeout*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return TimeoutPrototype__doRefresh(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__unrefCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -13633,6 +13653,32 @@ JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__unrefCallback, (JSGlobalObject * lexi return TimeoutPrototype__doUnref(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +extern "C" void TimeoutPrototype__argumentsSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSTimeout*>(JSValue::decode(thisValue)); + thisObject->m_arguments.set(vm, thisObject, JSValue::decode(value)); +} + +extern "C" EncodedJSValue TimeoutPrototype__argumentsGetCachedValue(JSC::EncodedJSValue thisValue) +{ + auto* thisObject = jsCast<JSTimeout*>(JSValue::decode(thisValue)); + return JSValue::encode(thisObject->m_arguments.get()); +} + +extern "C" void TimeoutPrototype__callbackSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSTimeout*>(JSValue::decode(thisValue)); + thisObject->m_callback.set(vm, thisObject, JSValue::decode(value)); +} + +extern "C" EncodedJSValue TimeoutPrototype__callbackGetCachedValue(JSC::EncodedJSValue thisValue) +{ + auto* thisObject = jsCast<JSTimeout*>(JSValue::decode(thisValue)); + return JSValue::encode(thisObject->m_callback.get()); +} + void JSTimeoutPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) { Base::finishCreation(vm); @@ -13717,6 +13763,39 @@ extern "C" EncodedJSValue Timeout__create(Zig::GlobalObject* globalObject, void* return JSValue::encode(instance); } + +template<typename Visitor> +void JSTimeout::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSTimeout* thisObject = jsCast<JSTimeout*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_arguments); + visitor.append(thisObject->m_callback); +} + +DEFINE_VISIT_CHILDREN(JSTimeout); + +template<typename Visitor> +void JSTimeout::visitAdditionalChildren(Visitor& visitor) +{ + JSTimeout* thisObject = this; + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + visitor.append(thisObject->m_arguments); + visitor.append(thisObject->m_callback); +} + +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSTimeout); + +template<typename Visitor> +void JSTimeout::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor) +{ + JSTimeout* thisObject = jsCast<JSTimeout*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + thisObject->visitAdditionalChildren<Visitor>(visitor); +} + +DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSTimeout); class JSTranspilerPrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h index fc9be7501..02fef6d3c 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.h +++ b/src/bun.js/bindings/ZigGeneratedClasses.h @@ -1575,6 +1575,13 @@ public: } void finishCreation(JSC::VM&); + + DECLARE_VISIT_CHILDREN; + template<typename Visitor> void visitAdditionalChildren(Visitor&); + DECLARE_VISIT_OUTPUT_CONSTRAINTS; + + mutable JSC::WriteBarrier<JSC::Unknown> m_arguments; + mutable JSC::WriteBarrier<JSC::Unknown> m_callback; }; class JSTranspiler final : public JSC::JSDestructibleObject { diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index a53d9bbe4..841e65b21 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -3988,6 +3988,50 @@ pub const JSTimeout = struct { return Timeout__fromJS(value); } + extern fn TimeoutPrototype__argumentsSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + extern fn TimeoutPrototype__argumentsGetCachedValue(JSC.JSValue) JSC.JSValue; + + /// `Timeout.arguments` setter + /// This value will be visited by the garbage collector. + pub fn argumentsSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + JSC.markBinding(@src()); + TimeoutPrototype__argumentsSetCachedValue(thisValue, globalObject, value); + } + + /// `Timeout.arguments` getter + /// This value will be visited by the garbage collector. + pub fn argumentsGetCached(thisValue: JSC.JSValue) ?JSC.JSValue { + JSC.markBinding(@src()); + const result = TimeoutPrototype__argumentsGetCachedValue(thisValue); + if (result == .zero) + return null; + + return result; + } + + extern fn TimeoutPrototype__callbackSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + extern fn TimeoutPrototype__callbackGetCachedValue(JSC.JSValue) JSC.JSValue; + + /// `Timeout.callback` setter + /// This value will be visited by the garbage collector. + pub fn callbackSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + JSC.markBinding(@src()); + TimeoutPrototype__callbackSetCachedValue(thisValue, globalObject, value); + } + + /// `Timeout.callback` getter + /// This value will be visited by the garbage collector. + pub fn callbackGetCached(thisValue: JSC.JSValue) ?JSC.JSValue { + JSC.markBinding(@src()); + const result = TimeoutPrototype__callbackGetCachedValue(thisValue); + if (result == .zero) + return null; + + return result; + } + /// Create a new instance of Timeout pub fn toJS(this: *Timeout, globalObject: *JSC.JSGlobalObject) JSC.JSValue { JSC.markBinding(@src()); @@ -4030,10 +4074,13 @@ pub const JSTimeout = struct { @compileLog("Expected Timeout.hasRef to be a callback but received " ++ @typeName(@TypeOf(Timeout.hasRef))); if (@TypeOf(Timeout.doRef) != CallbackType) @compileLog("Expected Timeout.doRef to be a callback but received " ++ @typeName(@TypeOf(Timeout.doRef))); + if (@TypeOf(Timeout.doRefresh) != CallbackType) + @compileLog("Expected Timeout.doRefresh to be a callback but received " ++ @typeName(@TypeOf(Timeout.doRefresh))); if (@TypeOf(Timeout.doUnref) != CallbackType) @compileLog("Expected Timeout.doUnref to be a callback but received " ++ @typeName(@TypeOf(Timeout.doUnref))); if (!JSC.is_bindgen) { @export(Timeout.doRef, .{ .name = "TimeoutPrototype__doRef" }); + @export(Timeout.doRefresh, .{ .name = "TimeoutPrototype__doRefresh" }); @export(Timeout.doUnref, .{ .name = "TimeoutPrototype__doUnref" }); @export(Timeout.finalize, .{ .name = "TimeoutClass__finalize" }); @export(Timeout.hasRef, .{ .name = "TimeoutPrototype__hasRef" }); diff --git a/src/bun.js/node/node.classes.ts b/src/bun.js/node/node.classes.ts index 5bebabe72..f984077e4 100644 --- a/src/bun.js/node/node.classes.ts +++ b/src/bun.js/node/node.classes.ts @@ -14,6 +14,10 @@ export default [ fn: "doRef", length: 0, }, + refresh: { + fn: "doRefresh", + length: 0, + }, unref: { fn: "doUnref", length: 0, @@ -27,6 +31,7 @@ export default [ length: 1, }, }, + values: ["arguments", "callback"], }), define({ name: "Stats", diff --git a/test/js/web/timers/setInterval.test.js b/test/js/web/timers/setInterval.test.js index 7b03afba5..b4215eef2 100644 --- a/test/js/web/timers/setInterval.test.js +++ b/test/js/web/timers/setInterval.test.js @@ -59,3 +59,17 @@ it("async setInterval", async () => { }); }); }); + +it("setInterval if refreshed before run, should reschedule to run later", done => { + let start = Date.now(); + let timer = setInterval(() => { + let end = Date.now(); + clearInterval(timer); + expect(end - start).toBeGreaterThanOrEqual(150); + done(); + }, 100); + + setTimeout(() => { + timer.refresh(); + }, 50); +}); diff --git a/test/js/web/timers/setTimeout.test.js b/test/js/web/timers/setTimeout.test.js index 88472adc7..dbe89dea8 100644 --- a/test/js/web/timers/setTimeout.test.js +++ b/test/js/web/timers/setTimeout.test.js @@ -171,3 +171,62 @@ it.skip("order of setTimeouts", done => { setTimeout(maybeDone(() => nums.push(4), 1)); Promise.resolve().then(maybeDone(() => nums.push(1))); }); + +it("setTimeout should refresh N times", done => { + let count = 0; + let timer = setTimeout(() => { + count++; + timer.refresh(); + }, 50); + + setTimeout(() => { + clearTimeout(timer); + expect(count).toBeGreaterThanOrEqual(5); + done(); + }, 300); +}); + +it("setTimeout if refreshed before run, should reschedule to run later", done => { + let start = Date.now(); + let timer = setTimeout(() => { + let end = Date.now(); + expect(end - start).toBeGreaterThanOrEqual(150); + done(); + }, 100); + + setTimeout(() => { + timer.refresh(); + }, 50); +}); + +it("setTimeout should refresh after already been run", done => { + let count = 0; + let timer = setTimeout(() => { + count++; + }, 50); + + setTimeout(() => { + timer.refresh(); + }, 100); + + setTimeout(() => { + expect(count).toBe(2); + done(); + }, 300); +}); + +it("setTimeout should not refresh after clearTimeout", done => { + let count = 0; + let timer = setTimeout(() => { + count++; + }, 50); + + clearTimeout(timer); + + timer.refresh(); + + setTimeout(() => { + expect(count).toBe(0); + done(); + }, 100); +}); |