From 057feb7e0a8b46e34dcf662f6c0c1bba409e004e Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Tue, 28 Feb 2023 17:07:29 -0500 Subject: [PATCH 1/4] gh-34: Ensures AudioContext Clock's stop() doesn't throw errors. --- src/js/audiocontext-clock.js | 32 ++++++---- tests/all-tests.html | 1 + tests/html/audiocontext-clock-tests.html | 40 ++++++++++++ tests/js/audiocontext-clock-tests.js | 80 ++++++++++++++++++++++++ tests/testem.json | 10 ++- 5 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 tests/html/audiocontext-clock-tests.html create mode 100644 tests/js/audiocontext-clock-tests.js diff --git a/src/js/audiocontext-clock.js b/src/js/audiocontext-clock.js index 6dc8fe2..4580376 100644 --- a/src/js/audiocontext-clock.js +++ b/src/js/audiocontext-clock.js @@ -81,7 +81,7 @@ var fluid = fluid || require("infusion"), scriptNode: { expander: { funcName: "berg.clock.autoAudioContext.createScriptNode", - args: ["{that}.context", "{that}.options.blockSize", "{that}.tick"] + args: ["{that}.context", "{that}.options.blockSize"] } } }, @@ -90,30 +90,36 @@ var fluid = fluid || require("infusion"), "onStart.startAudioContext": { priority: "after:updateState", funcName: "berg.clock.autoAudioContext.start", - args: ["{that}.context", "{that}.scriptNode"] + args: ["{that}"] }, "onStop.stopAudioContext": { priority: "after:updateState", funcName: "berg.clock.autoAudioContext.stop", - args: ["{that}.context", "{that}.scriptNode"] + args: ["{that}"] } } }); - berg.clock.autoAudioContext.createScriptNode = function (context, blockSize, tick) { - var sp = context.createScriptProcessor(blockSize, 1, 1); - sp.onaudioprocess = tick; - return sp; + berg.clock.autoAudioContext.createScriptNode = function (context, + blockSize) { + var scriptNode = context.createScriptProcessor(blockSize, 1, 1); + return scriptNode; }; - berg.clock.autoAudioContext.start = function (context, scriptNode) { - scriptNode.connect(context.destination); - context.resume(); + berg.clock.autoAudioContext.start = function (that) { + if (!that.model.isPlaying) { + that.scriptNode.connect(context.destination); + that.scriptNode.onaudioprocess = that.tick; + that.context.resume(); + } }; - berg.clock.autoAudioContext.stop = function (context, scriptNode) { - scriptNode.disconnect(context.destination); - scriptNode.onaudioprocess = undefined; + berg.clock.autoAudioContext.stop = function (that) { + if (that.model.isPlaying) { + that.scriptNode.disconnect(context.destination); + that.scriptNode.onaudioprocess = undefined; + that.audioContext.suspend(); + } }; })(); diff --git a/tests/all-tests.html b/tests/all-tests.html index 119b030..b7684d0 100644 --- a/tests/all-tests.html +++ b/tests/all-tests.html @@ -17,6 +17,7 @@ "html/realtime-clock-tests.html", "html/raf-clock-tests.html", "html/setinterval-clock-tests.html", + "html/audiocontext-clock-tests.html", "html/worker-setinterval-clock-tests.html", "html/clock-logger-tests.html", "html/priority-queue-tests.html", diff --git a/tests/html/audiocontext-clock-tests.html b/tests/html/audiocontext-clock-tests.html new file mode 100644 index 0000000..9350ee7 --- /dev/null +++ b/tests/html/audiocontext-clock-tests.html @@ -0,0 +1,40 @@ + + + + + AudioContext Clock Tests + + + + + + + + + + + + + + + + + + + + + + + +

AudioContext Clock Tests

+

+
+

+
    + + + + + diff --git a/tests/js/audiocontext-clock-tests.js b/tests/js/audiocontext-clock-tests.js new file mode 100644 index 0000000..78943e6 --- /dev/null +++ b/tests/js/audiocontext-clock-tests.js @@ -0,0 +1,80 @@ +/* + * Bergson AudioContext Clock Tests + * http://github.com/colinbdclark/bergson + * + * Copyright 2023, Colin Clark + * Dual licensed under the MIT and GPL Version 2 licenses. + */ +/*global require*/ +var fluid = fluid || require("infusion"), + berg = fluid.registerNamespace("berg"); + +(function () { + "use strict"; + + var QUnit = fluid.registerNamespace("QUnit"); + + fluid.registerNamespace("berg.test.AudioContextClock"); + + QUnit.module("AudioContext Clock Tests"); + + fluid.defaults("berg.test.clock.audioContext", { + gradeNames: "fluid.component", + + components: { + clock: { + type: "berg.clock.autoAudioContext", + options: { + freq: 1 + } + } + } + }); + + QUnit.test("Instantiation", function () { + let clock = berg.clock.autoAudioContext({ + freq: 1 + }); + QUnit.ok(clock, "Clock was successfully instantiated."); + }); + + QUnit.test("Start", function () { + let clock = berg.clock.autoAudioContext({ + freq: 1 + }); + + try { + clock.start(); + QUnit.ok(true, "Clock successfully started.") + } catch (e) { + QUnit.ok(false, "Clock failed to start successfully", e); + } + }); + + QUnit.test("Stop before start", function () { + let clock = berg.clock.autoAudioContext({ + freq: 1 + }); + + try { + clock.stop(); + QUnit.ok(true, "Calling stop() before starting has no effect."); + } catch (e) { + QUnit.ok(false, "Calling stop() before starting failed.", e); + } + }); + + QUnit.test("Stop after start", function () { + let clock = berg.clock.autoAudioContext({ + freq: 1 + }); + + try { + clock.start(); + clock.stop(); + QUnit.ok(true, "Clock successfully stopped after starting."); + } catch (e) { + QUnit.ok(false, "Calling stop() after starting failed.", e); + } + }); +})(); diff --git a/tests/testem.json b/tests/testem.json index 45a42d3..b418d16 100644 --- a/tests/testem.json +++ b/tests/testem.json @@ -1,5 +1,13 @@ { "test_page": "tests/all-tests.html", "timeout": 300, - "skip": "PhantomJS,IE,Headless Chrome" + "skip": "PhantomJS,IE", + "browser_args": { + "Chrome": [ + "--autoplay-policy=no-user-gesture-required" + ], + "Chrome Canary": [ + "--autoplay-policy=no-user-gesture-required" + ] + } } From 1f8f084996b57afa83c8efc1f8b7f8b55e26a489 Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Tue, 28 Feb 2023 19:14:36 -0500 Subject: [PATCH 2/4] Fixes gh-34, fixes gh-35. Properly implements start() and stop() for the AudioContext Clock. Adds unit tests for the AudioContext clock (which require the autoplay policy to be disabled to run successfully). --- package.json | 3 +- src/js/audiocontext-clock.js | 24 ++++++---- tests/all-tests.html | 2 - tests/html/audiocontext-clock-tests.html | 5 +- tests/js/audiocontext-clock-tests.js | 58 +++++++++++++----------- tests/js/utils/audiocontext-tester.js | 53 ++++++++++++---------- tests/js/utils/clock-test-utilities.js | 2 +- tests/testem-webaudio.json | 10 ++++ tests/testem.json | 10 +--- 9 files changed, 93 insertions(+), 74 deletions(-) create mode 100644 tests/testem-webaudio.json diff --git a/package.json b/package.json index 08754f4..8aeeb72 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ }, "scripts": { "prepare": "npx grunt", - "browser-test": "npx testem ci --file tests/testem.json", + "browser-webaudio-test": "npx testem ci --file tests/testem-webaudio.json", + "browser-test": "npm run browser-webaudio-test && npx testem ci --file tests/testem.json", "node-test": "node tests/node-all-tests.js", "test": "npm run node-test && npm run browser-test" } diff --git a/src/js/audiocontext-clock.js b/src/js/audiocontext-clock.js index 4580376..76f9dda 100644 --- a/src/js/audiocontext-clock.js +++ b/src/js/audiocontext-clock.js @@ -108,18 +108,24 @@ var fluid = fluid || require("infusion"), }; berg.clock.autoAudioContext.start = function (that) { - if (!that.model.isPlaying) { - that.scriptNode.connect(context.destination); - that.scriptNode.onaudioprocess = that.tick; - that.context.resume(); - } + that.scriptNode.connect(that.context.destination); + that.scriptNode.onaudioprocess = that.tick; + that.context.resume(); }; berg.clock.autoAudioContext.stop = function (that) { - if (that.model.isPlaying) { - that.scriptNode.disconnect(context.destination); - that.scriptNode.onaudioprocess = undefined; - that.audioContext.suspend(); + try { + that.scriptNode.disconnect(that.context.destination); + } catch (e) { + // Only swallow the error if was thrown because + // the script node wasn't connected, + // which can occur if stop() is called before start(). + if (e.name !== "InvalidAccessError") { + throw e; + } } + + that.scriptNode.onaudioprocess = undefined; + that.context.suspend(); }; })(); diff --git a/tests/all-tests.html b/tests/all-tests.html index b7684d0..a15d46d 100644 --- a/tests/all-tests.html +++ b/tests/all-tests.html @@ -10,14 +10,12 @@ - + + + - + diff --git a/tests/js/audiocontext-clock-tests.js b/tests/js/audiocontext-clock-tests.js index 78943e6..6d8eee9 100644 --- a/tests/js/audiocontext-clock-tests.js +++ b/tests/js/audiocontext-clock-tests.js @@ -18,56 +18,35 @@ var fluid = fluid || require("infusion"), QUnit.module("AudioContext Clock Tests"); - fluid.defaults("berg.test.clock.audioContext", { - gradeNames: "fluid.component", - - components: { - clock: { - type: "berg.clock.autoAudioContext", - options: { - freq: 1 - } - } - } - }); - QUnit.test("Instantiation", function () { - let clock = berg.clock.autoAudioContext({ - freq: 1 - }); + var clock = berg.clock.autoAudioContext(); QUnit.ok(clock, "Clock was successfully instantiated."); }); QUnit.test("Start", function () { - let clock = berg.clock.autoAudioContext({ - freq: 1 - }); + var clock = berg.clock.autoAudioContext(); try { clock.start(); - QUnit.ok(true, "Clock successfully started.") + QUnit.ok(true, "Clock successfully started."); } catch (e) { QUnit.ok(false, "Clock failed to start successfully", e); } }); QUnit.test("Stop before start", function () { - let clock = berg.clock.autoAudioContext({ - freq: 1 - }); + var clock = berg.clock.autoAudioContext(); try { clock.stop(); QUnit.ok(true, "Calling stop() before starting has no effect."); } catch (e) { - QUnit.ok(false, "Calling stop() before starting failed.", e); + QUnit.ok(false, "Calling stop() before starting failed: " + e.message); } }); QUnit.test("Stop after start", function () { - let clock = berg.clock.autoAudioContext({ - freq: 1 - }); + var clock = berg.clock.autoAudioContext(); try { clock.start(); @@ -77,4 +56,29 @@ var fluid = fluid || require("infusion"), QUnit.ok(false, "Calling stop() after starting failed.", e); } }); + + + fluid.defaults("berg.test.clock.autoAudioContextClockTestSuite", { + gradeNames: ["berg.test.clock.testSuite"], + + tests: [ + { + name: "Initial state, default options", + initOnly: true, + tester: { + type: "berg.test.clock.tester.audioContext" + } + }, + + { + name: "tick() time update", + tester: { + type: "berg.test.clock.tester.audioContext" + } + } + ] + }); + + var testSuite = berg.test.clock.autoAudioContextClockTestSuite(); + testSuite.run(); })(); diff --git a/tests/js/utils/audiocontext-tester.js b/tests/js/utils/audiocontext-tester.js index c84aaa2..35aefb6 100644 --- a/tests/js/utils/audiocontext-tester.js +++ b/tests/js/utils/audiocontext-tester.js @@ -29,19 +29,24 @@ }); berg.test.clock.testCase.audioContext.testInitial = function (clock, tester, maxJitter) { - QUnit.equal(clock.freq, tester.model.expectedFreq, + QUnit.equal(clock.freq, tester.options.expectedFreq, "The clock should be initialized with a freq of " + - tester.model.expectedFreq + "."); - berg.test.assertTimeEqual(clock.time, tester.model.expectedTime, maxJitter, + tester.options.expectedFreq + "."); + + berg.test.assertTimeEqual(clock.time, + tester.options.expectedInitialTime, + maxJitter, "The clock should be initialized with the current time."); - QUnit.equal(clock.tickDuration, tester.model.expectedTickDuration, + QUnit.equal(clock.tickDuration, + tester.options.expectedTickDuration, "The clock should have been initialized with a tick duration of " + - tester.model.expectedTickDuration + " seconds."); + tester.options.expectedTickDuration + " seconds."); }; berg.test.clock.testCase.audioContext.testTick = function (clock, time, maxJitter, tester) { - var expectedTime = tester.model.expectedTime + tester.model.expectedTickDuration; + var expectedTime = tester.model.expectedTime + + tester.options.expectedTickDuration; berg.test.assertTimeEqual(clock.time, expectedTime, maxJitter, "The clock's time should reflect the current expected time."); @@ -53,32 +58,32 @@ fluid.defaults("berg.test.clock.tester.audioContext", { gradeNames: [ - // TODO: The order of these two grades matters crucially. Why? "berg.test.clock.tester.external", "berg.test.clock.tester.realtime" ], - maxJitter: 0.05, + maxJitter: Number.EPSILON, - // TODO: These were moved into the model (instead of options) - // do to expansion issues. But all other testers expect to find - // these in the options. This should be normalized. - model: { - expectedTime: "{clock}.context.currentTime", - expectedFreq: { - expander: { - funcName: "berg.test.clock.tester.audioContext.calcFreq", - args: ["{clock}.context", "{clock}.options.blockSize"] - } - }, - expectedTickDuration: { - expander: { - funcName: "berg.test.clock.tester.audioContext.calcTickDuration", - args: ["{clock}.context", "{clock}.options.blockSize"] - } + expectedInitialTime: "{clock}.context.currentTime", + + expectedFreq: { + expander: { + funcName: "berg.test.clock.tester.audioContext.calcFreq", + args: ["{clock}.context", "{clock}.options.blockSize"] + } + }, + + expectedTickDuration: { + expander: { + funcName: "berg.test.clock.tester.audioContext.calcTickDuration", + args: ["{clock}.context", "{clock}.options.blockSize"] } }, + model: { + expectedTime: 0 + }, + components: { testCase: { type: "berg.test.clock.testCase.audioContext" diff --git a/tests/js/utils/clock-test-utilities.js b/tests/js/utils/clock-test-utilities.js index 75fcabc..784083d 100644 --- a/tests/js/utils/clock-test-utilities.js +++ b/tests/js/utils/clock-test-utilities.js @@ -33,7 +33,7 @@ var fluid = fluid || require("infusion"), " Expected time: " + expected + ", actual time was: " + actual + " Tolerance is " + tolerance + - "; difference was: " + diff + "ms."); + "; difference was: " + diff); }; berg.test.clock.manualTicker = function (numTicks, clock) { diff --git a/tests/testem-webaudio.json b/tests/testem-webaudio.json new file mode 100644 index 0000000..924f1f6 --- /dev/null +++ b/tests/testem-webaudio.json @@ -0,0 +1,10 @@ +{ + "test_page": "tests/html/audiocontext-clock-tests.html", + "timeout": 300, + "skip": "PhantomJS,IE,Firefox,Headless Firefox,Safari,Brave,Headless Brave,Headless Chrome", + "browser_args": { + "Chrome": [ + "--autoplay-policy=no-user-gesture-required" + ] + } +} diff --git a/tests/testem.json b/tests/testem.json index b418d16..09af89d 100644 --- a/tests/testem.json +++ b/tests/testem.json @@ -1,13 +1,5 @@ { "test_page": "tests/all-tests.html", "timeout": 300, - "skip": "PhantomJS,IE", - "browser_args": { - "Chrome": [ - "--autoplay-policy=no-user-gesture-required" - ], - "Chrome Canary": [ - "--autoplay-policy=no-user-gesture-required" - ] - } + "skip": "PhantomJS,IE" } From 47c3254f2b28eb5264e7a7e267508bcf2aa7bfec Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Wed, 1 Mar 2023 12:38:25 -0500 Subject: [PATCH 3/4] gh-35: Creates separate script entries for node, core, and Web Audio tests. Narrows the list of browsers we test with using Testem's launch option. --- package.json | 6 +++--- tests/testem-webaudio.json | 2 +- tests/testem.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8aeeb72..af40639 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,9 @@ }, "scripts": { "prepare": "npx grunt", - "browser-webaudio-test": "npx testem ci --file tests/testem-webaudio.json", - "browser-test": "npm run browser-webaudio-test && npx testem ci --file tests/testem.json", + "browser-core-test": "npx testem ci --file tests/testem.json", + "webaudio-test": "npx testem ci --file tests/testem-webaudio.json", "node-test": "node tests/node-all-tests.js", - "test": "npm run node-test && npm run browser-test" + "test": "npm run node-test && npm run webaudio-test && npm run browser-core-test" } } diff --git a/tests/testem-webaudio.json b/tests/testem-webaudio.json index 924f1f6..62b730a 100644 --- a/tests/testem-webaudio.json +++ b/tests/testem-webaudio.json @@ -1,7 +1,7 @@ { "test_page": "tests/html/audiocontext-clock-tests.html", "timeout": 300, - "skip": "PhantomJS,IE,Firefox,Headless Firefox,Safari,Brave,Headless Brave,Headless Chrome", + "launch": "Chrome", "browser_args": { "Chrome": [ "--autoplay-policy=no-user-gesture-required" diff --git a/tests/testem.json b/tests/testem.json index 09af89d..6f9a854 100644 --- a/tests/testem.json +++ b/tests/testem.json @@ -1,5 +1,5 @@ { "test_page": "tests/all-tests.html", "timeout": 300, - "skip": "PhantomJS,IE" + "launch": "Firefox,Chrome,Safari" } From 148f4187a14301ac5e99a5459ab9dbc3f790af12 Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Fri, 3 Mar 2023 09:33:08 -0500 Subject: [PATCH 4/4] gh-35: Improves test coverage and fine-tunes testem configurations. --- src/js/audiocontext-clock.js | 20 +++--- tests/{all-tests.html => core-tests.html} | 4 +- tests/js/audiocontext-clock-tests.js | 76 ++++++++++++----------- tests/testem.json | 4 +- 4 files changed, 55 insertions(+), 49 deletions(-) rename tests/{all-tests.html => core-tests.html} (92%) diff --git a/src/js/audiocontext-clock.js b/src/js/audiocontext-clock.js index 76f9dda..bf6afb1 100644 --- a/src/js/audiocontext-clock.js +++ b/src/js/audiocontext-clock.js @@ -90,13 +90,13 @@ var fluid = fluid || require("infusion"), "onStart.startAudioContext": { priority: "after:updateState", funcName: "berg.clock.autoAudioContext.start", - args: ["{that}"] + args: ["{that}.scriptNode", "{that}.context", "{that}.tick"] }, "onStop.stopAudioContext": { priority: "after:updateState", funcName: "berg.clock.autoAudioContext.stop", - args: ["{that}"] + args: ["{that}.scriptNode", "{that}.context"] } } }); @@ -107,15 +107,15 @@ var fluid = fluid || require("infusion"), return scriptNode; }; - berg.clock.autoAudioContext.start = function (that) { - that.scriptNode.connect(that.context.destination); - that.scriptNode.onaudioprocess = that.tick; - that.context.resume(); + berg.clock.autoAudioContext.start = function (scriptNode, context, tickFn) { + scriptNode.connect(context.destination); + scriptNode.onaudioprocess = tickFn; + context.resume(); }; - berg.clock.autoAudioContext.stop = function (that) { + berg.clock.autoAudioContext.stop = function (scriptNode, context) { try { - that.scriptNode.disconnect(that.context.destination); + scriptNode.disconnect(context.destination); } catch (e) { // Only swallow the error if was thrown because // the script node wasn't connected, @@ -125,7 +125,7 @@ var fluid = fluid || require("infusion"), } } - that.scriptNode.onaudioprocess = undefined; - that.context.suspend(); + scriptNode.onaudioprocess = undefined; + context.suspend(); }; })(); diff --git a/tests/all-tests.html b/tests/core-tests.html similarity index 92% rename from tests/all-tests.html rename to tests/core-tests.html index a15d46d..e35e287 100644 --- a/tests/all-tests.html +++ b/tests/core-tests.html @@ -2,7 +2,7 @@ - All Bergson Tests + Bergson Core Tests @@ -11,7 +11,7 @@