<!DOCTYPE html>
<html>
  <head>
    <title>
      audioparam-exceptional-values.html
    </title>
    <script src="/resources/testharness.js"></script>
    <script src="/resources/testharnessreport.js"></script>
    <script src="/webaudio/resources/audit-util.js"></script>
    <script src="/webaudio/resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let audit = Audit.createTaskRunner();

      // Context to use for all of the tests.  The context isn't used for any
      // processing; just need one for creating a gain node, which is used for
      // all the tests.
      let context;

      // For these values, AudioParam methods should throw a Typeerror because
      // they are not finite values.
      let nonFiniteValues = [Infinity, -Infinity, NaN];

      audit.define('initialize', (task, should) => {
        should(() => {
          // Context for testing.  Rendering isn't done, so any valid values can
          // be used here so might as well make them small.
          context = new OfflineAudioContext(1, 1, 8000);
        }, 'Creating context for testing').notThrow();

        task.done();
      });

      audit.define(
          {
            label: 'test value',
            description: 'Test non-finite arguments for AudioParam value'
          },
          (task, should) => {
            let gain = context.createGain();

            // Default method for generating the arguments for an automation
            // method for testing the value of the automation.
            let defaultFuncArg = (value) => [value, 1];

            // Test the value parameter
            doTests(should, gain, TypeError, nonFiniteValues, [
              {automationName: 'setValueAtTime', funcArg: defaultFuncArg}, {
                automationName: 'linearRampToValueAtTime',
                funcArg: defaultFuncArg
              },
              {
                automationName: 'exponentialRampToValueAtTime',
                funcArg: defaultFuncArg
              },
              {
                automationName: 'setTargetAtTime',
                funcArg: (value) => [value, 1, 1]
              }
            ]);
            task.done();
          });

      audit.define(
          {
            label: 'test time',
            description: 'Test non-finite arguments for AudioParam time'
          },
          (task, should) => {
            let gain = context.createGain();

            // Default method for generating the arguments for an automation
            // method for testing the time parameter of the automation.
            let defaultFuncArg = (startTime) => [1, startTime];

            // Test the time parameter
            doTests(should, gain, TypeError, nonFiniteValues, [
              {automationName: 'setValueAtTime', funcArg: defaultFuncArg},
              {
                automationName: 'linearRampToValueAtTime',
                funcArg: defaultFuncArg
              },
              {
                automationName: 'exponentialRampToValueAtTime',
                funcArg: defaultFuncArg
              },
              // Test start time for setTarget
              {
                automationName: 'setTargetAtTime',
                funcArg: (startTime) => [1, startTime, 1]
              },
              // Test time constant for setTarget
              {
                automationName: 'setTargetAtTime',
                funcArg: (timeConstant) => [1, 1, timeConstant]
              },
            ]);

            task.done();
          });

      audit.define(
          {
            label: 'test setValueCurve',
            description: 'Test non-finite arguments for setValueCurveAtTime'
          },
          (task, should) => {
            let gain = context.createGain();

            // Just an array for use by setValueCurveAtTime. The length and
            // contents of the array are not important.
            let curve = new Float32Array(3);

            doTests(should, gain, TypeError, nonFiniteValues, [
              {
                automationName: 'setValueCurveAtTime',
                funcArg: (startTime) => [curve, startTime, 1]
              },
            ]);

            // Non-finite values for the curve should signal an error
            doTests(
                should, gain, TypeError,
                [[1, 2, Infinity, 3], [1, NaN, 2, 3]], [{
                  automationName: 'setValueCurveAtTime',
                  funcArg: (c) => [c, 1, 1]
                }]);

            task.done();
          });

      audit.define(
          {
            label: 'special cases 1',
            description: 'Test exceptions for finite values'
          },
          (task, should) => {
            let gain = context.createGain();

            // Default method for generating the arguments for an automation
            // method for testing the time parameter of the automation.
            let defaultFuncArg = (startTime) => [1, startTime];

            // Test the time parameter
            let curve = new Float32Array(3);
            doTests(should, gain, RangeError, [-1], [
              {automationName: 'setValueAtTime', funcArg: defaultFuncArg},
              {
                automationName: 'linearRampToValueAtTime',
                funcArg: defaultFuncArg
              },
              {
                automationName: 'exponentialRampToValueAtTime',
                funcArg: defaultFuncArg
              },
              {
                automationName: 'setTargetAtTime',
                funcArg: (startTime) => [1, startTime, 1]
              },
              // Test time constant
              {
                automationName: 'setTargetAtTime',
                funcArg: (timeConstant) => [1, 1, timeConstant]
              },
              // startTime and duration for setValueCurve
              {
                automationName: 'setValueCurveAtTime',
                funcArg: (startTime) => [curve, startTime, 1]
              },
              {
                automationName: 'setValueCurveAtTime',
                funcArg: (duration) => [curve, 1, duration]
              },
            ]);

            // Two final tests for setValueCurve: duration must be strictly
            // positive.
            should(
                () => gain.gain.setValueCurveAtTime(curve, 1, 0),
                'gain.gain.setValueCurveAtTime(curve, 1, 0)')
                .throw(RangeError);
            should(
                () => gain.gain.setValueCurveAtTime(curve, 1, -1),
                'gain.gain.setValueCurveAtTime(curve, 1, -1)')
                .throw(RangeError);

            task.done();
          });

      audit.define(
          {
            label: 'special cases 2',
            description: 'Test special cases for expeonentialRamp'
          },
          (task, should) => {
            let gain = context.createGain();

            doTests(should, gain, RangeError, [0, -1e-100, 1e-100], [{
                      automationName: 'exponentialRampToValueAtTime',
                      funcArg: (value) => [value, 1]
                    }]);

            task.done();
          });

      audit.run();

      // Run test over the set of values in |testValues| for all of the
      // automation methods in |testMethods|.  The expected error type is
      // |errorName|. |testMethods| is an array of dictionaries with attributes
      // |automationName| giving the name of the automation method to be tested
      // and |funcArg| being a function of one parameter that produces an array
      // that will be used as the argument to the automation method.
      function doTests(should, node, errorName, testValues, testMethods) {
        testValues.forEach(value => {
          testMethods.forEach(method => {
            let args = method.funcArg(value);
            let message = 'gain.gain.' + method.automationName + '(' +
                argString(args) + ')';
            should(() => node.gain[method.automationName](...args), message)
                .throw(errorName);
          });
        });
      }

      // Specialized printer for automation arguments so that messages make
      // sense.  We assume the first element is either a number or an array.  If
      // it's an array, there are always three elements, and we want to print
      // out the brackets for the array argument.
      function argString(arg) {
        if (typeof(arg[0]) === 'number') {
          return arg.toString();
        }

        return '[' + arg[0] + '],' + arg[1] + ',' + arg[2];
      }
    </script>
  </body>
</html>
