From 1c2484b1b2e11cea83db088c53f84141f1f65a6a Mon Sep 17 00:00:00 2001 From: Harsh Kumar Choudhary Date: Fri, 15 Mar 2024 13:54:21 +0530 Subject: [PATCH 1/2] feat: adds test --- test/IdleQueue-test.mjs | 808 ++++++++++++++++++++++++++ test/IdleValue-test.mjs | 130 +++++ test/defineIdleProperties-test.mjs | 91 +++ test/defineIdleProperty-test.mjs | 73 +++ test/helpers.mjs | 35 ++ test/idle-callback-polyfills-test.mjs | 74 +++ test/index.html | 88 +++ 7 files changed, 1299 insertions(+) create mode 100644 test/IdleQueue-test.mjs create mode 100644 test/IdleValue-test.mjs create mode 100644 test/defineIdleProperties-test.mjs create mode 100644 test/defineIdleProperty-test.mjs create mode 100644 test/helpers.mjs create mode 100644 test/idle-callback-polyfills-test.mjs create mode 100644 test/index.html diff --git a/test/IdleQueue-test.mjs b/test/IdleQueue-test.mjs new file mode 100644 index 0000000..a6a11dc --- /dev/null +++ b/test/IdleQueue-test.mjs @@ -0,0 +1,808 @@ +/* + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IdleQueue } from '../dist/idleQueue.mjs' +import { rIC } from '../dist/idleCbWithPolyfill.mjs' +import { dispatchEvent, nextIdleCallback, when } from './helpers.mjs' + +const isSafari_ = !!(typeof safari === 'object' && safari.pushNotification) + +const sandbox = sinon.createSandbox() + +function blockingSpy(ms) { + return sandbox.stub().callsFake(() => { + const startTime = +new Date() + while (new Date() - startTime < ms) { + // Do nothing. + } + }) +} + +async function getIdleDeadlinePrototype() { + return await new Promise((resolve) => { + rIC(deadline => resolve(deadline.constructor.prototype)) + }) +} + +/** + * A wrapper around `sinon.stub()` that supports non-existent own properties. + * @param {!object} obj + * @param {string} prop + * @param {*} value + * @return {{value: !Function}} + */ +function stubProperty(obj, prop, value) { + if (!obj.hasOwnProperty(prop)) { + return { + value: (value) => { + Object.defineProperty(obj, prop, { value, configurable: true }) + }, + } + } + else { + return sandbox.stub(obj, prop) + } +} + +describe(`IdleQueue`, () => { + beforeEach(() => { + sandbox.restore() + stubProperty(document, 'visibilityState').value('visible') + }) + + afterEach(() => { + sandbox.restore() + }) + + describe(`constructor`, () => { + it(`accepts a defaultMinTaskTime option`, async () => { + const idleDeadlinePrototype = await getIdleDeadlinePrototype() + + let timeRemaining + sandbox.stub(idleDeadlinePrototype, 'timeRemaining').callsFake(() => { + return timeRemaining + }) + + const queue1 = new IdleQueue({ defaultMinTaskTime: 10 }) + const spy1 = sandbox.spy() + const rICSpy1 = sandbox.spy() + + timeRemaining = 9 + queue1.pushTask(spy1) + rIC(rICSpy1) + + // The added spy should not run because the timeRemaining value will + // always be less than the defaultMinTaskTime. + await when(() => rICSpy1.calledOnce) + assert(spy1.notCalled) + + // Simulate a longer idle period and assert spy1 is eventually called. + timeRemaining = 50 + await when(() => spy1.calledOnce) + + const queue2 = new IdleQueue({ defaultMinTaskTime: 25 }) + const spy2 = sandbox.spy() + const rICSpy2 = sandbox.spy() + + timeRemaining = 26 + + queue2.pushTask(spy2) + rIC(rICSpy2) + + await when(() => rICSpy2.calledOnce) + assert(spy1.calledOnce) + + queue1.destroy() + queue2.destroy() + }) + + it(`adds lifecycle event listeners when ensureTasksRun is true`, () => { + sandbox.spy(window, 'addEventListener') + + const queue = new IdleQueue({ ensureTasksRun: true }) + + assert(window.addEventListener.calledWith( + 'visibilitychange', + sinon.match.func, + true, + )) + + if (isSafari_) { + assert(window.addEventListener.calledWith( + 'beforeunload', + sinon.match.func, + true, + )) + } + else { + assert(!window.addEventListener.calledWith( + 'beforeunload', + sinon.match.func, + true, + )) + } + + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + + queue.pushTask(spy1) + queue.pushTask(spy2) + dispatchEvent(window, 'beforeunload') + + if (isSafari_) { + assert(spy1.calledOnce) + assert(spy2.calledOnce) + } + else { + assert(spy1.notCalled) + assert(spy2.notCalled) + } + + const spy3 = sandbox.spy() + const spy4 = sandbox.spy() + const spy5 = sandbox.spy() + + queue.pushTask(spy3) + queue.pushTask(spy4) + queue.pushTask(spy5) + + stubProperty(document, 'visibilityState').value('hidden') + dispatchEvent(document, 'visibilitychange') + + assert(spy3.calledOnce) + assert(spy4.calledOnce) + assert(spy5.calledOnce) + + queue.destroy() + }) + + it(`handles changes in lifecycle state while the queue is pending`, async () => { + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + const spy3 = sandbox.spy() + + const queue = new IdleQueue({ ensureTasksRun: true }) + + queue.pushTask(spy1) + queue.pushTask(spy2) + queue.pushTask(spy3) + + assert(spy1.notCalled) + assert(spy2.notCalled) + assert(spy3.notCalled) + + dispatchEvent(window, 'beforeunload') + + if (isSafari_) { + assert(spy1.calledOnce) + assert(spy2.calledOnce) + assert(spy3.calledOnce) + } + else { + assert(spy1.notCalled) + assert(spy2.notCalled) + assert(spy3.notCalled) + } + + stubProperty(document, 'visibilityState').value('hidden') + dispatchEvent(document, 'visibilitychange') + + assert(spy1.calledOnce) + assert(spy2.calledOnce) + assert(spy3.calledOnce) + + queue.destroy() + }) + }) + + describe(`pushTask`, () => { + it(`queues a task to run when idle`, async () => { + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + const spy3 = sandbox.spy() + + const queue = new IdleQueue() + + // Since this idle callback is scheduled before the spies are added, + // It should always run first. + rIC(() => { + assert(spy1.notCalled) + assert(spy2.notCalled) + assert(spy3.notCalled) + }) + + queue.pushTask(spy1) + queue.pushTask(spy2) + queue.pushTask(spy3) + + assert(spy1.notCalled) + assert(spy2.notCalled) + assert(spy3.notCalled) + + await when(() => spy3.calledOnce) + + assert(spy1.calledOnce) + assert(spy2.calledOnce) + assert(spy3.calledOnce) + + queue.destroy() + }) + + it(`calls the task with the state at add time`, async () => { + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + + const queue = new IdleQueue() + + const clock = sinon.useFakeTimers({ now: 1e12, toFake: ['Date'] }) + + stubProperty(document, 'visibilityState').value('hidden') + queue.pushTask(spy1) + + clock.tick(1000) + + stubProperty(document, 'visibilityState').value('visible') + queue.pushTask(spy2) + + clock.restore() + + assert(spy1.notCalled) + assert(spy2.notCalled) + + await when(() => spy2.calledOnce) + + assert(spy1.calledOnce) + assert.strictEqual(spy1.firstCall.args[0].time, 1e12) + assert.strictEqual(spy1.firstCall.args[0].visibilityState, 'hidden') + + assert(spy2.calledOnce) + assert.strictEqual(spy2.firstCall.args[0].time, 1e12 + 1000) + assert.strictEqual(spy2.firstCall.args[0].visibilityState, 'visible') + + queue.destroy() + }) + + it(`waits until the next idle period if all tasks cannot finish`, async () => { + const spy1 = blockingSpy(5) + const spy2 = blockingSpy(45) + const spy3 = blockingSpy(5) + const spy4 = blockingSpy(5) + const rICSpy = sandbox.spy() + + const queue = new IdleQueue() + + queue.pushTask(spy1) + queue.pushTask(spy2) + queue.pushTask(spy3) + queue.pushTask(spy4) + + // This callback is queued after the 4 spies, but it should run at some + // point before the last one (implying the queue needed to reschedule). + rIC(rICSpy) + + assert(spy1.notCalled) + assert(spy2.notCalled) + assert(spy3.notCalled) + assert(spy4.notCalled) + assert(rICSpy.notCalled) + + await when(() => spy4.calledOnce) + + assert(spy1.calledOnce) + assert(spy2.calledOnce) + assert(spy3.calledOnce) + assert(spy4.calledOnce) + + assert(rICSpy.calledOnce) + assert(rICSpy.calledBefore(spy4)) + + queue.destroy() + }) + + it(`runs the task as a microtask when in the hidden state if ensureTasksRun is true`, async () => { + stubProperty(document, 'visibilityState').value('hidden') + + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + const spy3 = sandbox.spy() + + const queue = new IdleQueue({ ensureTasksRun: true }) + + queue.pushTask(spy1) + queue.pushTask(spy2) + queue.pushTask(spy3) + + assert(spy1.notCalled) + assert(spy2.notCalled) + assert(spy3.notCalled) + + // next microtask + await Promise.resolve() + + assert(spy1.calledOnce) + assert(spy2.calledOnce) + assert(spy3.calledOnce) + + queue.destroy() + }) + + it(`runs tasks in order`, async () => { + const testQueueOrder = async (visibilityState) => { + stubProperty(document, 'visibilityState').value(visibilityState) + + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + const spy3 = sandbox.spy() + const queue = new IdleQueue() + + queue.pushTask(spy1) + queue.pushTask(spy2) + queue.pushTask(spy3) + + assert(spy1.notCalled) + assert(spy2.notCalled) + assert(spy3.notCalled) + + await when(() => spy3.calledOnce) + + assert(spy1.calledOnce) + assert(spy1.calledBefore(spy2)) + assert(spy2.calledOnce) + assert(spy2.calledBefore(spy3)) + assert(spy3.calledOnce) + + queue.destroy() + } + + await testQueueOrder('visible') + await testQueueOrder('hidden') + }) + + it(`runs nested tasks in order`, async () => { + const testQueueOrder = async (visibilityState) => { + stubProperty(document, 'visibilityState').value(visibilityState) + + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + const spy3 = sandbox.spy() + const spy4 = sandbox.spy() + const spy5 = sandbox.spy() + const spy6 = sandbox.spy() + + const queue = new IdleQueue() + + queue.pushTask(spy1) + queue.pushTask(() => { + queue.pushTask(() => { + spy4() + queue.pushTask(spy6) + }) + spy2() + }) + queue.pushTask(() => { + queue.pushTask(spy5) + spy3() + }) + + await when(() => spy6.calledOnce) + + assert(spy1.calledOnce) + assert(spy1.calledBefore(spy2)) + assert(spy2.calledOnce) + assert(spy2.calledBefore(spy3)) + assert(spy3.calledOnce) + assert(spy3.calledBefore(spy4)) + assert(spy4.calledOnce) + assert(spy4.calledBefore(spy5)) + assert(spy5.calledOnce) + assert(spy5.calledBefore(spy6)) + assert(spy6.calledOnce) + + queue.destroy() + } + + await testQueueOrder('visible') + await testQueueOrder('hidden') + }) + + it(`runs nested tasks in order across idle periods`, async () => { + const spy1 = blockingSpy(5) + const spy2 = blockingSpy(45) + const spy3 = blockingSpy(5) + const spy4 = blockingSpy(45) + const spy5 = blockingSpy(5) + const spy6 = blockingSpy(45) + + const queue = new IdleQueue() + + queue.pushTask(spy1) + queue.pushTask(() => { + queue.pushTask(() => { + spy4() + queue.pushTask(spy6) + }) + spy2() + }) + queue.pushTask(() => { + queue.pushTask(spy5) + spy3() + }) + + await when(() => spy6.calledOnce) + + assert(spy1.calledOnce) + assert(spy1.calledBefore(spy2)) + assert(spy2.calledOnce) + assert(spy2.calledBefore(spy3)) + assert(spy3.calledOnce) + assert(spy3.calledBefore(spy4)) + assert(spy4.calledOnce) + assert(spy4.calledBefore(spy5)) + assert(spy5.calledOnce) + assert(spy5.calledBefore(spy6)) + assert(spy6.calledOnce) + + queue.destroy() + }) + + it(`accepts a minTaskTime option`, async () => { + const idleDeadlinePrototype = await getIdleDeadlinePrototype() + + const queue = new IdleQueue() + + let timeRemaining + sandbox.stub(idleDeadlinePrototype, 'timeRemaining').callsFake(() => { + return timeRemaining + }) + + const spy1 = sandbox.spy() + const rICSpy1 = sandbox.spy() + + timeRemaining = 13 + queue.pushTask(spy1) + rIC(rICSpy1) + + // With the default minTaskTime, spy1 should be called before rICSpy1. + await when(() => rICSpy1.calledOnce) + assert(spy1.called) + + const spy2 = sandbox.spy() + const rICSpy2 = sandbox.spy() + + queue.pushTask(spy2, { minTaskTime: 25 }) + rIC(rICSpy2) + + // With a minTaskTime of 25, rICSpy should be called before spy1. + await when(() => rICSpy2.calledOnce) + assert(spy2.notCalled) + + // Simulate a longer idle period. + timeRemaining = 50 + + await when(() => spy2.calledOnce) + + queue.destroy() + }) + }) + + describe(`unshiftTask`, () => { + it(`adds a task to the beginning of the queue`, async () => { + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + const spy3 = sandbox.spy() + + const queue = new IdleQueue() + + // Since this idle callback is scheduled before the spies are added, + // It should always run first. + rIC(() => { + assert(spy1.notCalled) + assert(spy2.notCalled) + assert(spy3.notCalled) + }) + + queue.pushTask(spy1) + queue.pushTask(spy2) + queue.unshiftTask(spy3) + + assert(spy1.notCalled) + assert(spy2.notCalled) + assert(spy3.notCalled) + + await when(() => spy2.calledOnce) + + assert(spy1.calledOnce) + assert(spy2.calledOnce) + assert(spy3.calledOnce) + + assert(spy3.calledBefore(spy1)) + assert(spy1.calledBefore(spy2)) + + queue.destroy() + }) + }) + + describe(`clearPendingTasks`, () => { + it(`removes scheduled tasks from the queue`, async () => { + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + const spy3 = sandbox.spy() + + const queue = new IdleQueue() + + // Since this idle callback is scheduled before the spies are added, + // It should always run first. + rIC(() => { + assert(spy1.notCalled) + assert(spy2.notCalled) + assert(spy3.notCalled) + }) + + queue.pushTask(spy1) + queue.pushTask(spy2) + + assert(spy1.notCalled) + assert(spy2.notCalled) + + queue.clearPendingTasks() + queue.pushTask(spy3) + + await when(() => spy3.calledOnce) + + assert(spy3.calledOnce) + + // Assert spy1 and spy2 were actually cleared (and not run). + assert(spy1.notCalled) + assert(spy2.notCalled) + + queue.destroy() + }) + }) + + describe(`runTasksImmediately`, () => { + it(`runs all pending tasks synchronously`, () => { + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + const spy3 = sandbox.spy() + + const queue = new IdleQueue() + + queue.pushTask(spy1) + queue.pushTask(spy2) + queue.pushTask(spy3) + + assert(spy1.notCalled) + assert(spy2.notCalled) + assert(spy3.notCalled) + + queue.runTasksImmediately() + + assert(spy1.calledOnce) + assert(spy2.calledOnce) + assert(spy3.calledOnce) + + queue.destroy() + }) + + it(`works when the queue is already running`, async () => { + const spy1 = blockingSpy(5) + const spy2 = blockingSpy(45) + const spy3 = blockingSpy(5) + const spy4 = blockingSpy(45) + const spy5 = blockingSpy(5) + const spy6 = blockingSpy(45) + + const queue = new IdleQueue() + + queue.pushTask(spy1) + queue.pushTask(() => { + queue.pushTask(() => { + spy4() + queue.pushTask(spy6) + }) + spy2() + }) + queue.pushTask(() => { + queue.pushTask(spy5) + spy3() + }) + + // This should run at some point in the middle of the 6 spies running. + // Ensure that the remaining spies are called immediately. + rIC(() => { + assert(spy6.notCalled) + + queue.runTasksImmediately() + + assert(spy6.calledOnce) + }) + + await when(() => spy6.calledOnce) + + assert(spy1.calledOnce) + assert(spy1.calledBefore(spy2)) + assert(spy2.calledOnce) + assert(spy2.calledBefore(spy3)) + assert(spy3.calledOnce) + assert(spy3.calledBefore(spy4)) + assert(spy4.calledOnce) + assert(spy4.calledBefore(spy5)) + assert(spy5.calledOnce) + assert(spy5.calledBefore(spy6)) + assert(spy6.calledOnce) + + queue.destroy() + }) + + it(`cancels pending idle callbacks to not run tasks twice`, async () => { + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + const queue = new IdleQueue() + + queue.pushTask(spy1) + queue.pushTask(spy2) + + assert(spy1.notCalled) + assert(spy2.notCalled) + + queue.runTasksImmediately() + + assert(spy1.calledOnce) + assert(spy2.calledOnce) + + // Wait until the next idle point to assert the tasks weren't re-run. + await nextIdleCallback() + + assert(spy1.calledOnce) + assert(spy2.calledOnce) + + queue.destroy() + }) + }) + + describe(`hasPendingTasks`, () => { + it(`returns true if there are tasks in the queue`, async () => { + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + const spy3 = sandbox.spy() + + const queue = new IdleQueue() + + assert.strictEqual(queue.hasPendingTasks(), false) + + queue.pushTask(spy1) + queue.pushTask(spy2) + queue.pushTask(spy3) + + assert.strictEqual(queue.hasPendingTasks(), true) + + await when(() => spy3.calledOnce) + + assert.strictEqual(queue.hasPendingTasks(), false) + + queue.destroy() + }) + + it(`returns true after running if more tasks are still scheduled`, async () => { + const spy1 = blockingSpy(5) + const spy2 = blockingSpy(45) + const spy3 = blockingSpy(5) + const spy4 = blockingSpy(5) + + const queue = new IdleQueue() + + assert.strictEqual(queue.hasPendingTasks(), false) + + queue.pushTask(spy1) + queue.pushTask(spy2) + queue.pushTask(spy3) + queue.pushTask(spy4) + + assert.strictEqual(queue.hasPendingTasks(), true) + + // This callback is queued after the 4 spies, but it should run at some + // point before the last one (implying the queue needed to reschedule). + rIC(() => { + assert.strictEqual(queue.hasPendingTasks(), true) + }) + + await when(() => spy4.calledOnce) + + assert.strictEqual(queue.hasPendingTasks(), false) + + queue.destroy() + }) + }) + + describe(`getState`, () => { + it(`returns the state (at add time) of the currently running task`, async () => { + const queue = new IdleQueue() + + const stub1 = sandbox.stub().callsFake((state) => { + assert.strictEqual(queue.getState(), state) + assert.strictEqual(queue.getState().time, 1e12) + assert.strictEqual(queue.getState().visibilityState, 'hidden') + }) + const stub2 = sandbox.stub().callsFake((state) => { + assert.strictEqual(queue.getState(), state) + assert.strictEqual(queue.getState().time, 1e12 + 1000) + assert.strictEqual(queue.getState().visibilityState, 'visible') + }) + + const clock = sinon.useFakeTimers({ now: 1e12, toFake: ['Date'] }) + + stubProperty(document, 'visibilityState').value('hidden') + queue.pushTask(stub1) + + clock.tick(1000) + + stubProperty(document, 'visibilityState').value('visible') + queue.pushTask(stub2) + + clock.restore() + + assert(stub1.notCalled) + assert(stub2.notCalled) + + await when(() => stub2.calledOnce) + + assert(stub1.calledOnce) + assert(stub2.calledOnce) + + queue.destroy() + }) + + it(`returns null if no tasks are running`, () => { + const queue = new IdleQueue() + + assert.strictEqual(queue.getState(), null) + + queue.destroy() + }) + }) + + describe(`destroy`, () => { + it(`removes added lifecycle listeners when ensureTasksRun is true`, () => { + sandbox.spy(self, 'removeEventListener') + + const queue = new IdleQueue({ ensureTasksRun: true }) + assert(self.removeEventListener.notCalled) + + queue.destroy() + + assert(self.removeEventListener.calledWith( + 'visibilitychange', + sinon.match.func, + true, + )) + + if (isSafari_) { + assert(window.removeEventListener.calledWith( + 'beforeunload', + sinon.match.func, + true, + )) + } + else { + assert(!window.removeEventListener.calledWith( + 'beforeunload', + sinon.match.func, + true, + )) + } + }) + }) +}) diff --git a/test/IdleValue-test.mjs b/test/IdleValue-test.mjs new file mode 100644 index 0000000..873074a --- /dev/null +++ b/test/IdleValue-test.mjs @@ -0,0 +1,130 @@ +/* + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IdleValue } from '../dist/idleValue.mjs' +import { nextIdleCallback } from './helpers.mjs' + +const sandbox = sinon.createSandbox() + +describe(`IdleValue`, () => { + afterEach(() => { + sandbox.restore() + }) + + describe(`IdleValue`, () => { + describe(`constructor`, () => { + it(`initializes the value when idle`, async () => { + const initStub = sandbox.stub().returns('42') + new IdleValue(initStub) + + assert(initStub.notCalled) + + await nextIdleCallback() + + assert(initStub.calledOnce) + }) + }) + + describe(`getValue`, () => { + it(`returns the value immediately when already initialized`, async () => { + const initStub = sandbox.stub().returns('42') + const idleVal = new IdleValue(initStub) + + await nextIdleCallback() + assert(initStub.calledOnce) + + const val = idleVal.getValue() + + assert.strictEqual(val, '42') + }) + + it(`runs the init function immediately if the value not yet set`, () => { + const initStub = sandbox.stub().returns('42') + const idleVal = new IdleValue(initStub) + + assert(initStub.notCalled) + + const val = idleVal.getValue() + assert.strictEqual(val, '42') + assert(initStub.calledOnce) + }) + + it(`cancels the idle request if run before idle`, async () => { + const initStub = sandbox.stub().returns('42') + const idleVal = new IdleValue(initStub) + + const val = idleVal.getValue() + assert(initStub.calledOnce) + assert.strictEqual(val, '42') + + await nextIdleCallback() + + // Assert the init function wasn't called again. + assert(initStub.calledOnce) + }) + + it(`does not initialize the value more than once`, async () => { + const initStub = sandbox.stub().returns('42') + const idleVal = new IdleValue(initStub) + + let val = idleVal.getValue() + assert.strictEqual(val, '42') + assert(initStub.calledOnce) + + val = idleVal.getValue() + assert.strictEqual(val, '42', 'val is not 42') + assert(initStub.calledOnce, 'initStub was not called once') + + await nextIdleCallback() + + val = idleVal.getValue() + assert.strictEqual(val, '42') + assert(initStub.calledOnce) + }) + }) + + describe(`setValue`, () => { + it(`updates the value`, () => { + const initStub = sandbox.stub().returns('42') + const idleVal = new IdleValue(initStub) + + let val = idleVal.getValue() + assert.strictEqual(val, '42') + + idleVal.setValue('43') + + val = idleVal.getValue() + assert.strictEqual(val, '43') + }) + + it(`cancels the idle request if run before idle`, async () => { + const initStub = sandbox.stub().returns('42') + const idleVal = new IdleValue(initStub) + + idleVal.setValue('43') + assert(initStub.notCalled) + + const val = idleVal.getValue() + assert.strictEqual(val, '43') + assert(initStub.notCalled) + + await nextIdleCallback() + + assert(initStub.notCalled) + }) + }) + }) +}) diff --git a/test/defineIdleProperties-test.mjs b/test/defineIdleProperties-test.mjs new file mode 100644 index 0000000..dbb951c --- /dev/null +++ b/test/defineIdleProperties-test.mjs @@ -0,0 +1,91 @@ +import { defineIdleProperties } from '../dist/defineIdleProperties.mjs' +import { IdleValue } from '../dist/idleValue.mjs' + +const sandbox = sinon.createSandbox() + +describe(`defineIdleProperties`, () => { + afterEach(() => { + sandbox.restore() + }) + + describe(`defineIdleProperties`, () => { + it(`defines a property for each passed prop`, () => { + const obj = {} + const spy1 = sinon.spy() + const spy2 = sinon.spy() + const spy3 = sinon.spy() + + defineIdleProperties(obj, { + prop1: spy1, + prop2: spy2, + prop3: spy3, + }) + + for (let i = 1; i <= 3; ++i) { + assert(obj.hasOwnProperty(`prop${i}`)) + const descriptor = Object.getOwnPropertyDescriptor(obj, `prop${i}`) + + assert.equal(descriptor.configurable, true) + assert.equal(descriptor.enumerable, false) + assert(typeof descriptor.get === 'function') + assert(typeof descriptor.set === 'function') + } + }) + + it(`defines getters shadowing IdleValue#getValue for each prop`, () => { + sandbox.spy(IdleValue.prototype, 'getValue') + + const obj = {} + const initStub1 = sandbox.stub().returns('A') + const initStub2 = sandbox.stub().returns('B') + const initStub3 = sandbox.stub().returns('C') + + defineIdleProperties(obj, { + prop1: initStub1, + prop2: initStub2, + prop3: initStub3, + }) + + assert(IdleValue.prototype.getValue.notCalled) + + assert.equal(obj.prop1, 'A') + assert(IdleValue.prototype.getValue.calledOnce) + assert.equal(obj.prop2, 'B') + assert(IdleValue.prototype.getValue.calledTwice) + assert.equal(obj.prop3, 'C') + assert(IdleValue.prototype.getValue.calledThrice) + }) + + it(`defines setters shadowing IdleValue#setValue for each prop`, () => { + sandbox.spy(IdleValue.prototype, 'setValue') + + const obj = {} + const initStub1 = sandbox.stub().returns('A') + const initStub2 = sandbox.stub().returns('B') + const initStub3 = sandbox.stub().returns('C') + + defineIdleProperties(obj, { + prop1: initStub1, + prop2: initStub2, + prop3: initStub3, + }) + + assert(IdleValue.prototype.setValue.notCalled) + + obj.prop1 = 'A2' + assert(IdleValue.prototype.setValue.calledOnce) + assert(IdleValue.prototype.setValue.getCall(0).calledWith('A2')) + assert.equal(obj.prop1, 'A2') + + obj.prop2 = 'B2' + assert(IdleValue.prototype.setValue.calledTwice) + assert(IdleValue.prototype.setValue.getCall(1).calledWith('B2')) + assert.equal(obj.prop2, 'B2') + + obj.prop3 = 'C2' + assert(IdleValue.prototype.setValue.calledThrice) + assert(IdleValue.prototype.setValue.getCall(2).calledWith('C2')) + assert.equal(obj.prop3, 'C2') + }) + }) +}) diff --git a/test/defineIdleProperty-test.mjs b/test/defineIdleProperty-test.mjs new file mode 100644 index 0000000..c21a177 --- /dev/null +++ b/test/defineIdleProperty-test.mjs @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineIdleProperty } from '../dist/defineIdleProperty.mjs' +import { IdleValue } from '../dist/idleValue.mjs' + +const sandbox = sinon.createSandbox() + +describe(`defineIdleProperty`, () => { + afterEach(() => { + sandbox.restore() + }) + + describe(`defineIdleProperty`, () => { + it(`defines a property on the passed object`, () => { + const obj = {} + defineIdleProperty(obj, 'prop', sandbox.spy()) + + assert(obj.hasOwnProperty('prop')) + + const descriptor = Object.getOwnPropertyDescriptor(obj, 'prop') + + assert.equal(descriptor.configurable, true) + assert.equal(descriptor.enumerable, false) + assert(typeof descriptor.get === 'function') + assert(typeof descriptor.set === 'function') + }) + + it(`defines a getter that shadows IdleValue#getValue`, () => { + sandbox.spy(IdleValue.prototype, 'getValue') + + const initStub = sandbox.stub().returns('42') + + const obj = {} + defineIdleProperty(obj, 'prop', initStub) + + assert(IdleValue.prototype.getValue.notCalled) + + assert.equal(obj.prop, '42') + assert(IdleValue.prototype.getValue.calledOnce) + }) + + it(`defines a setter that shadows IdleValue#setValue`, () => { + sandbox.spy(IdleValue.prototype, 'setValue') + + const initStub = sandbox.stub().returns('42') + + const obj = {} + defineIdleProperty(obj, 'prop', initStub) + + assert(IdleValue.prototype.setValue.notCalled) + + obj.prop = 'newValue' + + assert(IdleValue.prototype.setValue.calledOnce) + assert(IdleValue.prototype.setValue.calledWith('newValue')) + assert.equal(obj.prop, 'newValue') + }) + }) +}) diff --git a/test/helpers.mjs b/test/helpers.mjs new file mode 100644 index 0000000..d1c606a --- /dev/null +++ b/test/helpers.mjs @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { rIC } from '../dist/idleCbWithPolyfill.mjs' + +export async function when(fn, intervalMillis = 100, retries = 20) { + for (let i = 0; i < retries; i++) { + const result = await fn() + if (result) + return + + await new Promise(resolve => setTimeout(resolve, intervalMillis)) + } + throw new Error(`${fn} didn't return true after ${retries} retries.`) +} + +export const nextIdleCallback = () => new Promise(res => rIC(res)) + +export function dispatchEvent(target, eventType) { + const event = new Event(eventType) + target.dispatchEvent(event) +} diff --git a/test/idle-callback-polyfills-test.mjs b/test/idle-callback-polyfills-test.mjs new file mode 100644 index 0000000..b0b2378 --- /dev/null +++ b/test/idle-callback-polyfills-test.mjs @@ -0,0 +1,74 @@ +/* + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { cIC, rIC } from '../dist/idleCbWithPolyfill.mjs' +import { when } from './helpers.mjs' + +const sandbox = sinon.createSandbox() + +describe(`idle-callback-polyfills`, () => { + describe(`rIC`, () => { + afterEach(() => { + sandbox.restore() + }) + + it(`accepts a function and calls it with an IdleDealine object`, async () => { + const spy = sandbox.spy() + + rIC(spy) + + await when(() => spy.calledOnce) + + assert(spy.calledWith(sinon.match({ + didTimeout: false, + timeRemaining: sinon.match.func, + }))) + }) + + it(`does not call the function in the current task`, () => { + const spy = sandbox.spy() + + rIC(spy) + assert(spy.notCalled) + }) + }) + + describe(`cIC`, () => { + afterEach(() => { + sandbox.restore() + }) + + it(`cancels a scheduled rIC`, async () => { + const spy1 = sandbox.spy() + const spy2 = sandbox.spy() + + const handle1 = rIC(spy1) + rIC(spy2) + + assert(spy1.notCalled) + assert(spy2.notCalled) + + cIC(handle1) + + // Idle callbacks are called in the order they're queued, so spy2 can + // only be called is either spy1 is called or the first rIC is cancelled. + await when(() => spy2.calledOnce) + + assert(spy1.notCalled) + assert(spy2.calledOnce) + }) + }) +}) diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..c8181e5 --- /dev/null +++ b/test/index.html @@ -0,0 +1,88 @@ + + + + + + Mocha Tests + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + From abd7221fd00b4df6da58dcd68094fdc73f884f23 Mon Sep 17 00:00:00 2001 From: Harsh Kumar Choudhary Date: Fri, 15 Mar 2024 19:58:05 +0530 Subject: [PATCH 2/2] change: adds idlize tests and removes test from CI for now --- .github/workflows/ci.yml | 32 ---------- test/index.html | 126 ++++++++++++++++----------------------- test/index.test.ts | 0 3 files changed, 52 insertions(+), 106 deletions(-) delete mode 100644 test/index.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb78617..955c850 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,35 +53,3 @@ jobs: - name: Typecheck run: nr typecheck - - test: - runs-on: ${{ matrix.os }} - - strategy: - matrix: - node: [lts/*] - os: [ubuntu-latest, windows-latest, macos-latest] - fail-fast: false - - steps: - - uses: actions/checkout@v3 - - - name: Install pnpm - uses: pnpm/action-setup@v2 - - - name: Set node ${{ matrix.node }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node }} - - - name: Setup - run: npm i -g @antfu/ni - - - name: Install - run: nci - - - name: Build - run: nr build - - - name: Test - run: nr test diff --git a/test/index.html b/test/index.html index c8181e5..b015fbb 100644 --- a/test/index.html +++ b/test/index.html @@ -1,88 +1,66 @@ - - - - Mocha Tests + + + Mocha Tests + + +
- - -
- - - - + - - - - + - + // Expose chai's assert globally. + self.assert = chai.assert + - - - - - + + + + + - - + + - - + failedTests.push({ + name: test.title, + result: false, + message: err.message, + stack: err.stack, + titles: flattenTitles(test) + }) + }) + } + + diff --git a/test/index.test.ts b/test/index.test.ts deleted file mode 100644 index e69de29..0000000