pacificclimate/climate-explorer-frontend

View on GitHub
src/core/__tests__/chart-generator-tests.js

Summary

Maintainability
C
1 day
Test Coverage
/* ***************************************************************
 * chart-generator-tests.js - tests for chart generation functions
 *
 * One test (sometimes with multiple parts) for each function in
 * chart-generator-tests.js. The tests have the same names and are
 * in the same order as the functions.
 *
 * test data from ./sample-API-results.js
 * validation functions from ./test-validators.js
 *****************************************************************/

import {formatYAxis,
        fixedPrecision,
        makePrecisionBySeries,
        tooltipAddTimeOfYear,
        makeTooltipDisplayNumbersWithUnits,
        timeseriesToAnnualCycleGraph,
        getMonthlyData,
        shortestUniqueTimeseriesNamingFunction,
        dataToLongTermAverageGraph,
        getAllTimestamps,
        nameAPICallParametersFunction,
        timeseriesToTimeseriesGraph} from '../chart-generators';
import {allDefinedObject,
        isRectangularArray,
        allDefinedArray} from '../__test_data__/test-validators';
import {monthlyTasmaxTimeseries,
        seasonalTasmaxTimeseries,
        annualTasmaxTimeseries,
        monthlyTasminTimeseries,
        monthlyPrTimeseries,
        tasminData,
        tasmaxData,
        metadataToArray} from '../__test_data__/sample-API-results';

jest.dontMock('../chart-generators');
jest.dontMock('../util');
jest.dontMock('lodash');

describe('formatYAxis', function () {
  it('formats a c3 y axis with units label', function () {
    const axis = formatYAxis('meters');
    expect(allDefinedObject(axis)).toBe(true);
    expect(axis.label).toEqual({ text: 'meters', position: 'outer-middle' });
    expect(axis.show).toEqual(true);
    expect(axis.tick.format(6.993)).toEqual(fixedPrecision(6.993));
  });
});

describe('fixedPrecision', function () {
  it('formats a positive number for user display', function () {
    const formatted = fixedPrecision(6.22222);
    expect(formatted).toEqual(6.22);
  });
  it('formats a negative number for user display', function () {
    const formatted = fixedPrecision(-6.3333);
    expect(formatted).toEqual(-6.33);
  });
  it('rounds a number for user display', function () {
    const formatted = fixedPrecision(6.9999);
    expect(formatted).toEqual(7);
  });
});

describe('makePrecisionBySeries', function () {
  // this test fails and is skipped because it relies on an external
  // .yaml config file that isn't easily available during jest testing.
  // In non-test usage the file is transformed and made available by webpack.
  xit('reads the config file and applies its settings', function () {
    const precision = makePrecisionBySeries({ testseries: 'tasmin' });
    expect(precision(4.777, 'testseries')).toEqual(4.8);
  });
  it('uses a default precision for unspecified variables', function () {
    const precision = makePrecisionBySeries({ testseries: 'tasmin' });
    expect(precision(4.777, 'testseries')).toEqual(4.78);
  });
});

describe('tooltipAddTimeOfYear', function() {
  it('substitutes in the name of a month', function () {
    expect(tooltipAddTimeOfYear("Monthly Bills", undefined, 100, 6)).toBe("July Bills");
    expect(tooltipAddTimeOfYear("Income Monthly", undefined, 600, 3)).toBe("Income April");
  });
  it('substitutes in the name of a season', function () {
    expect(tooltipAddTimeOfYear("Seasonal Harvest", undefined, 600, 1)).toBe("Winter-DJF Harvest");
  });
  it('does nothing when not needed', function () {
    expect(tooltipAddTimeOfYear("Eggs per Chicken", undefined, 10, 10)).toBe("Eggs per Chicken");
  });
});

describe('makeTooltipDisplayNumbersWithUnits', function () {
  let axis = {};
  axis.y = formatYAxis('meters');
  let axes = {};
  const series1 = 'height';
  const series2 = 'depth';
  axes[series1] = 'y';
  let tooltipFunction;
  it('displays unit labels when there is a single data series', function () {
    tooltipFunction = makeTooltipDisplayNumbersWithUnits(axes, axis);
    expect(tooltipFunction(5, 0, series1, 0)).toEqual('5 meters');
  });
  it('displays unit labels when there are multiple data series', function () {
    axes[series2] = 'y';
    tooltipFunction = makeTooltipDisplayNumbersWithUnits(axes, axis);
    expect(tooltipFunction(6.22, 0, series1, 0)).toEqual('6.22 meters');
    expect(tooltipFunction(7.8, 0, series2, 0)).toEqual('7.8 meters');
  });
  it('displays unit labels when there are multiple unit types', function () {
    const series3 = 'weight';
    axis.y2 = formatYAxis('kilograms');
    axes[series3] = 'y2';
    tooltipFunction = makeTooltipDisplayNumbersWithUnits(axes, axis);
    expect(tooltipFunction(9.73, 0, series1, 0)).toEqual('9.73 meters');
    expect(tooltipFunction(-2.4, 0, series2, 0)).toEqual('-2.4 meters');
    expect(tooltipFunction(100000, 0, series3, 0)).toEqual('100000 kilograms');
  });
});

describe('timeseriesToAnnualCycleGraph', function () {
  const metadata = metadataToArray();
  it('rejects data sets with too many units', function () {
    let fakeData = JSON.parse(JSON.stringify(monthlyTasminTimeseries));
    fakeData.units = 'meters';
    function func () {
      timeseriesToAnnualCycleGraph(metadata, fakeData,
          monthlyTasmaxTimeseries, monthlyPrTimeseries);
    };
    expect(func).toThrow();
  });
  it('displays a single timeseries', function () {
    const c = timeseriesToAnnualCycleGraph(metadata, monthlyTasmaxTimeseries);
    expect(allDefinedObject(c)).toBe(true);
    expect(c.data.columns.length).toEqual(1);
    expect(c.data.columns[0].length).toEqual(13);
    expect(c.axis.x).toBeDefined();
    expect(c.axis.y).toBeDefined();
    expect(c.axis.y2).not.toBeDefined();
  });
  it('displays monthly, seasonal, and annual timeseries together', function () {
    const c = timeseriesToAnnualCycleGraph(metadata, monthlyTasmaxTimeseries,
        seasonalTasmaxTimeseries, annualTasmaxTimeseries);
    expect(allDefinedObject(c)).toBe(true);
    expect(isRectangularArray(c.data.columns, 3, 13)).toBe(true);
    expect(allDefinedArray(c.data.columns)).toBe(true);
    expect(c.axis.x).toBeDefined();
    expect(c.axis.y).toBeDefined();
    expect(c.axis.y2).not.toBeDefined();
  });
  it('displays two different variables at once', function () {
    const c = timeseriesToAnnualCycleGraph(metadata, monthlyTasmaxTimeseries,
        monthlyTasminTimeseries);
    expect(allDefinedObject(c)).toBe(true);
    expect(isRectangularArray(c.data.columns, 2, 13)).toBe(true);
    expect(allDefinedArray(c.data.columns)).toBe(true);
    expect(c.axis.x).toBeDefined();
    expect(c.axis.y).toBeDefined();
    expect(c.axis.y2).toBeDefined();
  });
});

describe('getMonthlyData', function () {
  it('rejects data with an unsupported time resolution', function () {
    let seventeen = {};
    for (let i = 0; i < 17; i++) {
      seventeen[Date(i)] = i * 3;
    }
    const tooMany = function () {getMonthlyData(seventeen);};
    expect(tooMany).toThrow();
  });
  it('rejects data with inconsistent time resolution', function () {
    const inconsistent = function () {
      getMonthlyData(monthlyTasmaxTimeseries.data, 'yearly');
    };
    expect(inconsistent).toThrow();
  });
  it('processes a monthly timeseries', function () {
    const processed = getMonthlyData(monthlyTasmaxTimeseries.data, 'monthly');
    expect(processed.length).toEqual(12);
    expect(processed[5]).toEqual(11.841563876512202);
    expect(processed[11]).toEqual(-16.96361296358877);
  });
  it('processes a seasonal timeseries', function () {
    const processed = getMonthlyData(seasonalTasmaxTimeseries.data, 'seasonal');
    expect(processed.length).toEqual(12);
    expect(processed[0]).toEqual(processed[11]);
  });
  it('processes an annual timeseries', function () {
    const processed = getMonthlyData(annualTasmaxTimeseries.data, 'yearly');
    expect(processed.length).toEqual(12);
    expect(processed[0]).toEqual(processed[7]);
    expect(processed[4]).toEqual(processed[11]);
  });
});

describe('shortestUniqueTimeseriesNamingFunction', function () {
  const metadata = metadataToArray();
  it('rejects identical time series', function () {
    const minimalMetadata = [{ unique_id: 'foo', md: 'bar' }, { unique_id: 'baz', md: 'bar' }];
    const minimalData = [{ id: 'foo' }, { id: 'baz' }];
    function func () {
      shortestUniqueTimeseriesNamingFunction(minimalMetadata, minimalData);
    };
    expect(func).toThrow();
  });
  it('uses a a default naming scheme for a single data series', function () {
    const nameFunction = shortestUniqueTimeseriesNamingFunction(metadata,
        [monthlyTasmaxTimeseries]);
    expect(nameFunction(metadata[0])).toEqual('Monthly Mean');
  });
  it('names series by time resolution', function () {
    const nameFunction = shortestUniqueTimeseriesNamingFunction(metadata,
        [monthlyTasmaxTimeseries, seasonalTasmaxTimeseries,
          annualTasmaxTimeseries]);
    expect(nameFunction(metadata[0])).toEqual('Monthly Mean');
    expect(nameFunction(metadata[1])).toEqual('Seasonal Mean');
    expect(nameFunction(metadata[2])).toEqual('Yearly Mean');
  });
  it('names series by variable', function () {
    const nameFunction = shortestUniqueTimeseriesNamingFunction(metadata,
        [monthlyTasmaxTimeseries, monthlyTasminTimeseries]);
    expect(nameFunction(metadata[0])).toEqual('Tasmax Mean');
    expect(nameFunction(metadata[3])).toEqual('Tasmin Mean');
  });
});

describe('dataToLongTermAverageGraph', function () {
  it('rejects datasets with missing metadata', function () {
    function func () {
      dataToLongTermAverageGraph(
        [tasmaxData, tasminData]);};
    expect(func).toThrow();
  });
  it('graphs a single data series', function () {
    const c = dataToLongTermAverageGraph([tasmaxData]);
    expect(allDefinedObject(c)).toBe(true);
    expect(c.data.columns.length).toEqual(2);
    expect(c.data.columns[0].length).toEqual(7);
    expect(c.axis.x).toBeDefined();
    expect(c.axis.y).toBeDefined();
    expect(c.axis.y2).not.toBeDefined();
  });
  it('graphs two data series', function () {
    const tasmaxQuery = { variable_id: 'tasmax', model_id: 'bcc-csm1-1-m' };
    const tasminQuery = { variable_id: 'tasmin', model_id: 'bcc-csm1-1-m' };
    const c = dataToLongTermAverageGraph(
        [tasmaxData, tasminData],
        [tasmaxQuery, tasminQuery]);
    expect(allDefinedObject(c)).toBe(true);
    expect(isRectangularArray(c.data.columns, 3, 7)).toBe(true);
    expect(allDefinedArray(c.data.columns)).toBe(true);
    expect(c.axis.x).toBeDefined();
    expect(c.axis.y).toBeDefined();
    expect(c.axis.y2).toBeDefined();
  });
  it('throw an error on more than two data series with different variables', function () {
    const tasmaxQuery = { variable_id: 'tasmax', model_id: 'bcc-csm1-1-m' };
    const tasminQuery = { variable_id: 'tasmin', model_id: 'bcc-csm1-1-m' };
    const prQuery = { variable_id: 'pr', model_id: 'bcc-csm1-1-m' };
    function func () {
      dataToLongTermAverageGraph(
        [tasmaxData, tasminData, prData],
        [tasmaxQuery, tasminQuery, prQuery]);
    };
    expect(func).toThrow();
  });
});

describe('getAllTimestamps', function () {
  it('throws an error if there is no data', function () {
    function func () {getAllTimestamps([]);};
    expect(func).toThrow();
  });
  it('throws an error if there are no available timestamps', function () {
    function func () {getAllTimestamps([{ r1p1i1: { data: {} } }]);};
    expect(func).toThrow();
  });
  it('returns timestamps associated with a data API call', function () {
    const stamps = getAllTimestamps([tasmaxData]);
    expect(stamps.length).toBe(6);
  });
  it('combines timestamps from multiple data API calls', function () {
    let fakeData = JSON.parse(JSON.stringify(tasminData));
    fakeData.r1i1p1.data = { '1990-04-01T00:00:00Z': 20, '1997-01-15T00:00:00Z': 0 };
    const stamps = getAllTimestamps([tasmaxData, fakeData]);
    expect(stamps.length).toBe(7);
  });
  it('extracts timestamps from timeseries API calls', function () {
    const stamps = getAllTimestamps([monthlyTasmaxTimeseries,
      seasonalTasmaxTimeseries]);
    expect(stamps.length).toBe(16);
  });
});

describe('nameAPICallParametersFunction', function () {
  it('refuses identical data sets', function () {
    function func () {
      nameAPICallParametersFunction(
        [{ variable: 'foo' }, { variable: 'foo' }]);};
    expect(func).toThrow();
  });
  it('refuses data sets calculated over different areas', function () {
    function func () {
      nameAPICallParametersFunction(
        [{ area: 'POLYGON+((-114,+-113,+-103,+-104+63,+-114+63))' },
         { area: 'POLYGON+((-115,+-113,+-103,+-105+63,+-115+63))' }]);};
    expect(func).toThrow();
  });
  it('assigns distinct names to data sets', function () {
    const tasmaxQuery = { variable_id: 'tasmax', model_id: 'bcc-csm1-1-m' };
    const tasminQuery = { variable_id: 'tasmin', model_id: 'bcc-csm1-1-m' };
    const nameFunction = nameAPICallParametersFunction([tasmaxQuery, tasminQuery]);
    expect(nameFunction('r1i1p1', tasmaxQuery)).toBe('tasmax r1i1p1');
    expect(nameFunction('r1i1p1', tasminQuery)).toBe('tasmin r1i1p1');
  });
});

describe('timeseriesToTimeSeriesGraph', function () {
  const metadata = metadataToArray();
  it('rejects data sets with too many units', function () {
    let fakeData = JSON.parse(JSON.stringify(monthlyTasminTimeseries));
    fakeData.units = 'meters';
    function func () {
      timeseriesToTimeseriesGraph(metadata, fakeData,
          monthlyTasmaxTimeseries, monthlyPrTimeseries);
    };
    expect(func).toThrow();
  });
  it('displays a single timeseries', function () {
    const c = timeseriesToTimeseriesGraph(metadata, monthlyTasmaxTimeseries);
    expect(allDefinedObject(c)).toBe(true);
    expect(c.data.columns[0][0]).toMatch('x');
    expect(c.data.columns.length).toEqual(2);
    expect(c.data.columns[0].length).toEqual(13);
    expect(c.axis.x).toBeDefined();
    expect(c.axis.y).toBeDefined();
    expect(c.axis.y2).not.toBeDefined();
  });
  it('displays two timeseries with different units', function () {
    const c = timeseriesToTimeseriesGraph(metadata, monthlyTasmaxTimeseries,
        monthlyPrTimeseries);
    expect(allDefinedObject(c)).toBe(true);
    expect(c.data.columns[0][0]).toMatch('x');
    expect(isRectangularArray(c.data.columns, 3, 13)).toBe(true);
    expect(allDefinedArray(c.data.columns)).toBe(true);
    expect(c.axis.x).toBeDefined();
    expect(c.axis.y).toBeDefined();
    expect(c.axis.y2).toBeDefined();
  });
});