From c89529f44239dcbd3d0762df38b29953963e4620 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sun, 10 Jun 2018 10:25:06 -0700 Subject: [PATCH] Refactor Milkdrop --- js/components/App.js | 2 + js/components/MilkdropWindow/Background.js | 26 ++ js/components/MilkdropWindow/Milkdrop.js | 202 +++++++++++ js/components/MilkdropWindow/PresetOverlay.js | 4 + js/components/MilkdropWindow/Presets.js | 102 ++++++ .../MilkdropWindow/__tests__/Presets.test.js | 173 +++++++++ js/components/MilkdropWindow/index.js | 336 ++++-------------- package.json | 3 +- yarn.lock | 4 + 9 files changed, 577 insertions(+), 275 deletions(-) create mode 100644 js/components/MilkdropWindow/Background.js create mode 100644 js/components/MilkdropWindow/Milkdrop.js create mode 100644 js/components/MilkdropWindow/Presets.js create mode 100644 js/components/MilkdropWindow/__tests__/Presets.test.js diff --git a/js/components/App.js b/js/components/App.js index c5e198a9..15195835 100644 --- a/js/components/App.js +++ b/js/components/App.js @@ -120,6 +120,7 @@ class App extends React.Component { analyser={media.getAnalyser()} width={width} height={height} + playing={this.props.status === "PLAYING"} /> )} @@ -154,6 +155,7 @@ App.propTypes = { }; const mapStateToProps = state => ({ + status: state.media.status, focused: state.windows.focused, closed: state.display.closed, genWindowsInfo: state.windows.genWindows diff --git a/js/components/MilkdropWindow/Background.js b/js/components/MilkdropWindow/Background.js new file mode 100644 index 00000000..9561d892 --- /dev/null +++ b/js/components/MilkdropWindow/Background.js @@ -0,0 +1,26 @@ +import React from "react"; + +const Background = props => { + const { innerRef, ...restProps } = props; + return ( +
+ ); +}; + +export default Background; diff --git a/js/components/MilkdropWindow/Milkdrop.js b/js/components/MilkdropWindow/Milkdrop.js new file mode 100644 index 00000000..7abd6e67 --- /dev/null +++ b/js/components/MilkdropWindow/Milkdrop.js @@ -0,0 +1,202 @@ +import React from "react"; +import screenfull from "screenfull"; +import PresetOverlay from "./PresetOverlay"; +import Background from "./Background"; + +const USER_PRESET_TRANSITION_SECONDS = 5.7; +const PRESET_TRANSITION_SECONDS = 2.7; +const MILLISECONDS_BETWEEN_PRESET_TRANSITIONS = 15000; + +export default class Milkdrop extends React.Component { + constructor(props) { + super(props); + this.state = { + isFullscreen: false, + presetOverlay: false + }; + this._handleFocusedKeyboardInput = this._handleFocusedKeyboardInput.bind( + this + ); + this._handleFullscreenChange = this._handleFullscreenChange.bind(this); + } + + async componentDidMount() { + this.setState({ currentPreset: this.props.presets.getCurrentIndex() }); + this.visualizer = this.props.butterchurn.createVisualizer( + this.props.analyser.context, + this._canvasNode, + { + width: this.props.width, + height: this.props.height, + pixelRatio: window.devicePixelRatio || 1 + } + ); + this._setRendererSize(this.props.width, this.props.height); + + this.visualizer.connectAudio(this.props.analyser); + + // Kick off the animation loop + const loop = () => { + if (this.props.playing) { + this.visualizer.render(); + } + this._animationFrameRequest = window.requestAnimationFrame(loop); + }; + loop(); + + this.presetCycle = true; + this._unsubscribeFocusedKeyDown = this.props.onFocusedKeyDown( + this._handleFocusedKeyboardInput + ); + screenfull.onchange(this._handleFullscreenChange); + } + + componentWillUnmount() { + this._pauseViz(); + this._stopCycling(); + if (this._unsubscribeFocusedKeyDown) { + this._unsubscribeFocusedKeyDown(); + } + screenfull.off("change", this._handleFullscreenChange); + } + + componentDidUpdate(prevProps) { + if ( + this.props.width !== prevProps.width || + this.props.height !== prevProps.height + ) { + this._setRendererSize(this.props.width, this.props.height); + } + } + + _pauseViz() { + if (this._animationFrameRequest) { + window.cancelAnimationFrame(this._animationFrameRequest); + this._animationFrameRequest = null; + } + } + + _stopCycling() { + if (this.cycleInterval) { + clearInterval(this.cycleInterval); + this.cycleInterval = null; + } + } + + _restartCycling() { + this._stopCycling(); + + if (this.presetCycle) { + this.cycleInterval = setInterval(() => { + this._nextPreset(PRESET_TRANSITION_SECONDS); + }, MILLISECONDS_BETWEEN_PRESET_TRANSITIONS); + } + } + + _setRendererSize(width, height) { + this._canvasNode.width = width; + this._canvasNode.height = height; + this.visualizer.setRendererSize(width, height); + } + + _handleFullscreenChange() { + if (screenfull.isFullscreen) { + this._setRendererSize(window.innerWidth, window.innerHeight); + } else { + this._setRendererSize(this.props.width, this.props.height); + } + this.setState({ isFullscreen: screenfull.isFullscreen }); + } + + _handleRequestFullsceen() { + if (screenfull.enabled) { + if (!screenfull.isFullscreen) { + screenfull.request(this._wrapperNode); + } else { + screenfull.exit(); + } + } + } + + _handleFocusedKeyboardInput(e) { + switch (e.keyCode) { + case 32: // spacebar + this._nextPreset(USER_PRESET_TRANSITION_SECONDS); + break; + case 8: // backspace + this._prevPreset(0); + break; + case 72: // H + this._nextPreset(0); + break; + case 82: // R + this.props.presets.toggleRandomize(); + break; + case 76: // L + this.setState({ presetOverlay: !this.state.presetOverlay }); + e.stopPropagation(); + break; + case 145: // scroll lock + case 125: // F14 (scroll lock for OS X) + this.presetCycle = !this.presetCycle; + this._restartCycling(); + break; + } + } + + async _nextPreset(blendTime) { + this.selectPreset(await this.props.presets.next(), blendTime); + } + + async _prevPreset(blendTime) { + this.selectPreset(await this.props.presets.previous(), blendTime); + } + + selectPreset(preset, blendTime = 0) { + if (preset != null) { + this.visualizer.loadPreset(preset, blendTime); + this._restartCycling(); + this.setState({ currentPreset: this.props.presets.getCurrentIndex() }); + } + } + + closePresetOverlay() { + this.setState({ presetOverlay: false }); + } + + render() { + const width = this.state.isFullscreen + ? window.innerWidth + : this.props.width; + const height = this.state.isFullscreen + ? window.innerHeight + : this.props.height; + return ( + (this._wrapperNode = node)} + onDoubleClick={() => this._handleRequestFullsceen()} + > + {this.state.presetOverlay && ( + this.props.onFocusedKeyDown(listener)} + selectPreset={async idx => { + this.selectPreset(await this.props.presets.selectIndex(idx), 0); + }} + closeOverlay={() => this.closePresetOverlay()} + /> + )} + (this._canvasNode = node)} + /> + + ); + } +} diff --git a/js/components/MilkdropWindow/PresetOverlay.js b/js/components/MilkdropWindow/PresetOverlay.js index e1a9aac4..91e61f46 100644 --- a/js/components/MilkdropWindow/PresetOverlay.js +++ b/js/components/MilkdropWindow/PresetOverlay.js @@ -8,16 +8,19 @@ class PresetOverlay extends React.Component { this ); } + componentDidMount() { this._unsubscribeFocusedKeyDown = this.props.onFocusedKeyDown( this._handleFocusedKeyboardInput ); } + componentWillUnmount() { if (this._unsubscribeFocusedKeyDown) { this._unsubscribeFocusedKeyDown(); } } + _handleFocusedKeyboardInput(e) { switch (e.keyCode) { case 38: // up arrow @@ -43,6 +46,7 @@ class PresetOverlay extends React.Component { break; } } + render() { if (!this.props.presetKeys) { return ( diff --git a/js/components/MilkdropWindow/Presets.js b/js/components/MilkdropWindow/Presets.js new file mode 100644 index 00000000..69acc746 --- /dev/null +++ b/js/components/MilkdropWindow/Presets.js @@ -0,0 +1,102 @@ +function getRandomIndex(arr) { + return Math.floor(Math.random() * arr.length); +} +function getRandomValue(arr) { + return arr[getRandomIndex(arr)]; +} + +function getLast(arr) { + return arr[arr.length - 1]; +} + +/** + * Track a collection of async loaded presets + * + * Presets can be changed via `next`, `previous` or `selectIndex`. In each case, + * a promise is returned. If the promise resolves to `null` it means the + * selection was canceled by a subsequent request before it could be fulfilled. + * If the request is successful, the promise resolves to the selected preset. + * + * We assume a model where some portion of the preset are supplied at initialization + * and the remainder can be loaded async via the function `getRest`. + */ +export default class Presets { + constructor({ keys, initialPresets, getRest, randomize = true }) { + this._keys = keys; // Alphabetical list of preset names + this._presets = initialPresets; // Presets indexed by name + this._getRest = getRest; // An async function to get the rest of the presets + this._history = []; // Indexes into _keys + + this._randomize = randomize; + + // Initialize with a key that we already have. + const avaliableKeys = Object.keys(initialPresets); + const currentKey = randomize + ? getRandomValue(avaliableKeys) + : avaliableKeys[0]; + this._currentIndex = this._keys.indexOf(currentKey); + this._history.push(this._currentIndex); + } + + toggleRandomize() { + this._randomize = !this._randomize; + } + + setRandomize(val) { + this._randomize = val; + } + + async next() { + let idx; + if (this._randomize || this._history.length === 0) { + idx = getRandomIndex(this._keys); + } else { + idx = (getLast(this._history) + 1) % this._keys.length; + } + this._history.push(idx); + return this._selectIndex(idx); + } + + async previous() { + if (this._history.length > 1) { + this._history.pop(); + return this._selectIndex(getLast(this._history)); + } + // We are at the very beginning. There is no "previous" preset. + return Promise.resolve(); + } + + async selectIndex(idx) { + // The public version of this method must add to the history + this._history.push(idx); + return this._selectIndex(idx); + } + + async _selectIndex(idx) { + const preset = this._presets[this._keys[idx]]; + if (!preset) { + const rest = await this._getRest(); + this._presets = Object.assign(this._presets, rest); + if (getLast(this._history) !== idx) { + // This selection must be obsolete. Return null so that + // the caller knows this request got canceled. + return null; + } + } + this._currentIndex = idx; + return this.getCurrent(); + } + + getKeys() { + return this._keys; + } + + getCurrentIndex() { + return this._currentIndex; + } + + getCurrent() { + // #matryoshka + return this._presets[this._keys[this._currentIndex]]; + } +} diff --git a/js/components/MilkdropWindow/__tests__/Presets.test.js b/js/components/MilkdropWindow/__tests__/Presets.test.js new file mode 100644 index 00000000..b3080e23 --- /dev/null +++ b/js/components/MilkdropWindow/__tests__/Presets.test.js @@ -0,0 +1,173 @@ +import { mockRandom } from "jest-mock-random"; +import Presets from "../Presets"; + +let presets; +beforeEach(() => { + mockRandom([0.0]); + presets = new Presets({ + keys: ["a", "b"], + initialPresets: { a: "Preset A", b: "Preset B" }, + randomize: true + }); +}); + +describe("initialize", () => { + beforeEach(() => {}); + test("picks a random value", () => { + expect(presets.getCurrent()).toBe("Preset A"); + }); + + test("picks another random value", () => { + mockRandom([0.9]); + presets = new Presets({ + keys: ["a", "b"], + initialPresets: { a: "Preset A", b: "Preset B" }, + randomize: true + }); + expect(presets.getCurrent()).toBe("Preset B"); + }); + + test("picks its random value from the initial presets", () => { + presets = new Presets({ + keys: ["a", "b", "c", "d", "e", "f", "g", "h"], + initialPresets: { a: "Preset A" }, + randomize: true + }); + expect(presets.getCurrent()).toBe("Preset A"); + }); +}); + +describe("next", () => { + test("picks a 'random' preset", async () => { + mockRandom([0.9]); + presets.next(); + expect(presets.getCurrent()).toBe("Preset B"); + + mockRandom([0.0]); + presets.next(); + expect(presets.getCurrent()).toBe("Preset A"); + }); + + test("picks the next key", async () => { + presets.setRandomize(false); + presets.next(); + expect(presets.getCurrent()).toBe("Preset B"); + }); + + test("wraps around", async () => { + presets.setRandomize(false); + presets.next(); + presets.next(); + expect(presets.getCurrent()).toBe("Preset A"); + }); +}); + +describe("previous", () => { + test("picks the previous key", async () => { + presets.setRandomize(false); + presets.next(); + presets.previous(); + expect(presets.getCurrent()).toBe("Preset A"); + }); + + test("does nothing when you are on the first item", async () => { + presets.previous(); + expect(presets.getCurrent()).toBe("Preset A"); + }); + + test("can do sequential previouses", async () => { + mockRandom([0.0]); + presets = new Presets({ + keys: ["a", "b", "c", "d"], + initialPresets: { + a: "Preset A", + b: "Preset B", + c: "Preset C", + d: "Preset D" + }, + randomize: false + }); + presets.next(); // b + presets.next(); // c + presets.next(); // d + presets.previous(); // c + presets.previous(); // b + presets.previous(); // a + expect(presets.getCurrent()).toBe("Preset A"); + }); + + test("will successfully resolve an unloaded preset", async () => { + mockRandom([0.0]); + presets = new Presets({ + keys: ["a", "b", "c", "d"], + initialPresets: { + a: "Preset A" + }, + randomize: false, + getRest: () => + Promise.resolve({ + b: "Preset B", + c: "Preset C" + }) + }); + presets.next(); // b + presets.next(); // c + const final = await presets.previous(); // b + expect(final).toBe("Preset B"); + }); +}); + +describe("getRest", () => { + beforeEach(() => { + mockRandom([0.0]); + presets = new Presets({ + keys: ["a", "b"], + initialPresets: { a: "Preset A" }, + getRest: () => + Promise.resolve({ + b: "Preset B" + }) + }); + }); + test("will get the rest of the presets if needed", async () => { + mockRandom([0.9]); + const resolved = presets.next(); + expect(presets.getCurrent()).toBe("Preset A"); + await resolved; + expect(presets.getCurrent()).toBe("Preset B"); + }); + + test("next (loading), previous brings us back to where we started", async () => { + presets.setRandomize(false); + presets.next(); + expect(presets.getCurrent()).toBe("Preset A"); + await presets.previous(); + expect(presets.getCurrent()).toBe("Preset A"); + }); +}); + +describe("selectIndex", () => { + test("adds an entry to the history", async () => { + presets.selectIndex(1); + presets.previous(); + expect(presets.getCurrent()).toBe("Preset A"); + }); +}); + +describe("getCurrentIndex", () => { + test("gets the active index while loading", async () => { + presets = new Presets({ + keys: ["a", "b"], + initialPresets: { a: "Preset A" }, + randomize: false, + getRest: () => + Promise.resolve({ + b: "Preset B" + }) + }); + const resolved = presets.next(); + expect(presets.getCurrentIndex()).toBe(0); + await resolved; + expect(presets.getCurrentIndex()).toBe(1); + }); +}); diff --git a/js/components/MilkdropWindow/index.js b/js/components/MilkdropWindow/index.js index a9ddaf4a..930005e6 100644 --- a/js/components/MilkdropWindow/index.js +++ b/js/components/MilkdropWindow/index.js @@ -1,26 +1,47 @@ import React from "react"; -import { connect } from "react-redux"; -import screenfull from "screenfull"; -import PresetOverlay from "./PresetOverlay"; +import Presets from "./Presets"; +import Milkdrop from "./Milkdrop"; +import Background from "./Background"; -const USER_PRESET_TRANSITION_SECONDS = 5.7; -const PRESET_TRANSITION_SECONDS = 2.7; -const MILLISECONDS_BETWEEN_PRESET_TRANSITIONS = 15000; - -class MilkdropWindow extends React.Component { - constructor(props) { - super(props); - this.state = { - isFullscreen: false, - presetOverlay: false, - currentPreset: -1 - }; - this._handleFocusedKeyboardInput = this._handleFocusedKeyboardInput.bind( - this - ); - this._handleFullscreenChange = this._handleFullscreenChange.bind(this); +// This component is just responsible for loading dependencies. +// This simplifies the inner component, by allowing +// it to alwasy assume that it has it's dependencies. +export default class PresetsLoader extends React.Component { + constructor() { + super(); + this.state = { presets: null, butterchurn: null }; } - componentDidMount() { + + async componentDidMount() { + const { + butterchurn, + presetKeys, + minimalPresets + } = await loadInitialDependencies(); + + this.setState({ + butterchurn, + presets: new Presets({ + keys: presetKeys, + initialPresets: minimalPresets, + getRest: loadNonMinimalPresets + }) + }); + } + + render() { + const { butterchurn, presets } = this.state; + const loaded = butterchurn != null && presets != null; + return loaded ? ( + + ) : ( + + ); + } +} + +async function loadInitialDependencies() { + return new Promise((resolve, reject) => { require.ensure( [ "butterchurn", @@ -28,265 +49,32 @@ class MilkdropWindow extends React.Component { "butterchurn-presets/lib/butterchurnPresetPackMeta.min" ], require => { - const analyserNode = this.props.analyser; const butterchurn = require("butterchurn"); const butterchurnMinimalPresets = require("butterchurn-presets/lib/butterchurnPresetsMinimal.min"); const presetPackMeta = require("butterchurn-presets/lib/butterchurnPresetPackMeta.min"); - this.presets = butterchurnMinimalPresets.getPresets(); - this.minmalPresetKeys = Object.keys(this.presets); - this.presetKeys = presetPackMeta.getMainPresetMeta().presets; - const presetKey = this.minmalPresetKeys[ - Math.floor(Math.random() * this.minmalPresetKeys.length) - ]; - - this.visualizer = butterchurn.createVisualizer( - analyserNode.context, - this._canvasNode, - { - width: this.props.width, - height: this.props.height, - pixelRatio: window.devicePixelRatio || 1 - } - ); - this._canvasNode.width = this.props.width; - this._canvasNode.height = this.props.height; - this.visualizer.connectAudio(analyserNode); - this.visualizer.loadPreset(this.presets[presetKey], 0); - // Kick off the animation loop - const loop = () => { - if (this.props.status === "PLAYING") { - this.visualizer.render(); - } - window.requestAnimationFrame(loop); - }; - loop(); - - const presetIdx = this.presetKeys.indexOf(presetKey); - this.presetHistory = [presetIdx]; - this.presetRandomize = true; - this.presetCycle = true; - this._restartCycling(); - this._unsubscribeFocusedKeyDown = this.props.onFocusedKeyDown( - this._handleFocusedKeyboardInput - ); - this.setState({ currentPreset: presetIdx }); - }, - e => { - console.error("Error loading Butterchurn", e); + resolve({ + butterchurn, + minimalPresets: butterchurnMinimalPresets.getPresets(), + presetKeys: presetPackMeta.getMainPresetMeta().presets + }); }, + reject, "butterchurn" ); - } - componentWillUnmount() { - this._pauseViz(); - this._stopCycling(); - if (this._unsubscribeFocusedKeyDown) { - this._unsubscribeFocusedKeyDown(); - } - screenfull.off("change", this._handleFullscreenChange); - } - componentDidUpdate(prevProps) { - if ( - this.props.width !== prevProps.width || - this.props.height !== prevProps.height - ) { - this._setRendererSize(this.props.width, this.props.height); - } - } - _pauseViz() { - if (this.animationFrameRequest) { - window.cancelAnimationFrame(this.animationFrameRequest); - this.animationFrameRequest = null; - } - } - _stopCycling() { - if (this.cycleInterval) { - clearInterval(this.cycleInterval); - this.cycleInterval = null; - } - } - _restartCycling() { - this._stopCycling(); - - if (this.presetCycle) { - this.cycleInterval = setInterval(() => { - this._nextPreset(PRESET_TRANSITION_SECONDS); - }, MILLISECONDS_BETWEEN_PRESET_TRANSITIONS); - } - } - _setRendererSize(width, height) { - this._canvasNode.width = width; - this._canvasNode.height = height; - // It's possible that the visualizer has not been intialized yet. - if (this.visualizer != null) { - this.visualizer.setRendererSize(width, height); - } - } - _handleFullscreenChange() { - if (screenfull.isFullscreen) { - this._setRendererSize(window.innerWidth, window.innerHeight); - } else { - this._setRendererSize(this.props.width, this.props.height); - } - this.setState({ isFullscreen: screenfull.isFullscreen }); - } - _handleRequestFullsceen() { - if (screenfull.enabled) { - if (!screenfull.isFullscreen) { - screenfull.request(this._wrapperNode); - } else { - screenfull.exit(); - } - } - } - _handleFocusedKeyboardInput(e) { - switch (e.keyCode) { - case 32: // spacebar - this._nextPreset(USER_PRESET_TRANSITION_SECONDS); - break; - case 8: // backspace - this._prevPreset(0); - break; - case 72: // H - this._nextPreset(0); - break; - case 82: // R - this.presetRandomize = !this.presetRandomize; - break; - case 76: // L - this.setState({ presetOverlay: !this.state.presetOverlay }); - e.stopPropagation(); - break; - case 145: // scroll lock - case 125: // F14 (scroll lock for OS X) - this.presetCycle = !this.presetCycle; - this._restartCycling(); - break; - } - } - async _loadMainPresetPack() { - this.loadingPresets = true; - return require.ensure( - ["butterchurn-presets/lib/butterchurnPresetsNonMinimal.min"], - require => { - const butterchurnNonMinimalPresets = require("butterchurn-presets/lib/butterchurnPresetsNonMinimal.min"); - Object.assign(this.presets, butterchurnNonMinimalPresets.getPresets()); - this.loadingPresets = false; - }, - e => { - console.error("Error loading Butterchurn Presets", e); - }, - "butterchurn-presets" - ); - } - async _nextPreset(blendTime) { - // The visualizer may not have initialized yet. - if (this.visualizer != null) { - let presetIdx; - if (this.presetRandomize || this.presetHistory.length === 0) { - presetIdx = Math.floor(this.presetKeys.length * Math.random()); - } else { - const prevPresetIdx = this.presetHistory[this.presetHistory.length - 1]; - presetIdx = (prevPresetIdx + 1) % this.presetKeys.length; - } - this.selectPreset(presetIdx, blendTime); - } - } - _prevPreset(blendTime) { - if (this.loadingPresets && this.presetQueue.length > 1) { - this.presetQueue.pop(); - return; - } - - if (this.presetHistory.length > 1 && this.visualizer != null) { - this.presetHistory.pop(); - const prevPreset = this.presetHistory[this.presetHistory.length - 1]; - this.visualizer.loadPreset( - this.presets[this.presetKeys[prevPreset]], - blendTime - ); - this._restartCycling(); - this.setState({ currentPreset: prevPreset }); - } - } - async selectPreset(presetIdx, blendTime) { - if (this.loadingPresets) { - this.presetQueue.push(presetIdx); - return; - } - - let preset = this.presets[this.presetKeys[presetIdx]]; - let selectedIdx; - if (!preset) { - this.presetQueue = [presetIdx]; - await this._loadMainPresetPack(); - if (this.presetQueue.length === 0) { - return; - } - selectedIdx = this.presetQueue[this.presetQueue.length - 1]; - preset = this.presets[this.presetKeys[selectedIdx]]; - this.presetHistory = this.presetHistory.concat(this.presetQueue); - } else { - selectedIdx = presetIdx; - this.presetHistory.push(selectedIdx); - } - this.visualizer.loadPreset(preset, blendTime); - this._restartCycling(); - this.setState({ currentPreset: selectedIdx }); - } - closePresetOverlay() { - this.setState({ presetOverlay: false }); - } - render() { - const width = this.state.isFullscreen - ? window.innerWidth - : this.props.width; - const height = this.state.isFullscreen - ? window.innerHeight - : this.props.height; - return ( -
(this._wrapperNode = node)} - onDoubleClick={() => this._handleRequestFullsceen()} - > - {this.state.presetOverlay && ( - this.props.onFocusedKeyDown(listener)} - selectPreset={idx => this.selectPreset(idx, 0)} - closeOverlay={() => this.closePresetOverlay()} - /> - )} - (this._canvasNode = node)} - /> -
- ); - } + }); } -const mapStateToProps = state => ({ - status: state.media.status -}); - -export default connect(mapStateToProps)(MilkdropWindow); +async function loadNonMinimalPresets() { + return new Promise((resolve, reject) => { + require.ensure( + ["butterchurn-presets/lib/butterchurnPresetsNonMinimal.min"], + require => { + resolve( + require("butterchurn-presets/lib/butterchurnPresetsNonMinimal.min").getPresets() + ); + }, + reject, + "butterchurn-presets" + ); + }); +} diff --git a/package.json b/package.json index 533193a1..4413b680 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "weight": "npm run build-library > /dev/null && gzip-size built/webamp.bundle.min.js", "test": "jest --projects config/jest.unit.js config/jest.eslint.js", "travis-tests": "npm run test && npm run build && npm run build-library", - "tdd": "jest --watch", + "tdd": "jest --projects config/jest.unit.js --watch", "format": "prettier --write experiments/**/*.js js/**/*.js css/**/*.css !css/**/*.min.css", "build-skin": "rm skins/base-2.91.wsz && cd skins/base-2.91 && zip -x .* -x 'Skining Updates.txt' -r ../base-2.91.wsz .", "build-skin-png": "rm skins/base-2.91-png.wsz && cd skins/base-2.91-png && zip -x .* -x 'Skining Updates.txt' -r ../base-2.91-png.wsz .", @@ -74,6 +74,7 @@ "http-server": "^0.11.1", "invariant": "^2.2.3", "jest": "^23.0.0", + "jest-mock-random": "^1.0.2", "jest-runner-eslint": "^0.4.0", "jsmediatags": "^3.8.1", "jszip": "^3.1.3", diff --git a/yarn.lock b/yarn.lock index bebc0434..c27177be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4341,6 +4341,10 @@ jest-message-util@^23.0.0: slash "^1.0.0" stack-utils "^1.0.1" +jest-mock-random@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/jest-mock-random/-/jest-mock-random-1.0.2.tgz#81a1aa641fdb3a049bf64e2a7a0411fd8fc3fb20" + jest-mock@^23.0.0: version "23.0.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-23.0.0.tgz#d9d897a1b74dc05c66a737213931496215897dd8"