diff --git a/client/__tests__/util/nameCreators.test.js b/client/__tests__/util/nameCreators.test.js new file mode 100644 index 00000000..5c6f462c --- /dev/null +++ b/client/__tests__/util/nameCreators.test.js @@ -0,0 +1,68 @@ +/* +Test cases for nameCreators.js. +*/ +import { + layoutDimensionName, + obsAnnoDimensionName, + diffexpDimensionName, + userDefinedDimensionName, + makeContinuousDimensionName +} from "../../src/util/nameCreators"; + +describe("nameCreators", () => { + const nameCreators = [ + layoutDimensionName, + obsAnnoDimensionName, + diffexpDimensionName, + userDefinedDimensionName + ]; + + test("check for namespace isolation", () => { + const foo = "foo"; + nameCreators.forEach(fn => expect(fn(foo)).not.toBe(foo)); + + const bar = "bar"; + nameCreators.forEach(fn => expect(fn(foo)).not.toBe(fn(bar))); + + nameCreators.forEach(fn => { + const allOtherFn = nameCreators.filter(elmnt => elmnt !== fn); + allOtherFn.forEach(otherFn => expect(fn(foo)).not.toBe(otherFn(foo))); + }); + }); + + test("check for legal keys", () => { + /* need namespace creators to return strings only */ + nameCreators.forEach(fn => expect(fn("X")).toMatch(/X/)); + }); +}); + +describe("makeContinuousDimensionName", () => { + test("namespaces", () => { + expect(makeContinuousDimensionName({ isObs: true }, "OK")).toMatch(/OK/); + expect(makeContinuousDimensionName({ isDiffExp: true }, "OK")).toMatch( + /OK/ + ); + expect(makeContinuousDimensionName({ isUserDefined: true }, "OK")).toMatch( + /OK/ + ); + }); + + test("error handling", () => { + expect(() => makeContinuousDimensionName({}, "oops")).toThrow(); + expect(() => + makeContinuousDimensionName( + { isObs: false, isDiffExp: false, isUserDefined: false }, + "oops" + ) + ).toThrow(); + expect(() => + makeContinuousDimensionName({ isObs: true }, "OK") + ).not.toThrow(); + expect(() => + makeContinuousDimensionName({ isDiffExp: true }, "OK") + ).not.toThrow(); + expect(() => + makeContinuousDimensionName({ isUserDefined: true }, "OK") + ).not.toThrow(); + }); +}); diff --git a/client/__tests__/util/stateManager/world.test.js b/client/__tests__/util/stateManager/world.test.js index b7a6aa4a..4cae7872 100644 --- a/client/__tests__/util/stateManager/world.test.js +++ b/client/__tests__/util/stateManager/world.test.js @@ -3,15 +3,40 @@ import * as Universe from "../../../src/util/stateManager/universe"; import * as World from "../../../src/util/stateManager/world"; import Crossfilter from "../../../src/util/typedCrossfilter"; import * as REST from "./sampleResponses"; -import { obsAnnoDimensionName } from "../../../src/util/nameCreators"; +import { + obsAnnoDimensionName, + layoutDimensionName +} from "../../../src/util/nameCreators"; +import * as kvCache from "../../../src/util/stateManager/keyvalcache"; /* -TODO: endpoints to test: - -createObsDimensionMap(crossfilter, world) -subsetVarData(world, universe, varData) - +Helper - creates universe, world, corssfilter and dimensionMap from +the default REST test response. */ +const defaultBigBang = () => { + /* create unverse, world, crossfilter and dimensionMap */ + /* create universe */ + const universe = Universe.createUniverseFromRestV02Response( + REST.config, + REST.schema, + REST.annotationsObs, + REST.annotationsVar, + REST.layoutObs + ); + /* create world */ + const world = World.createWorldFromEntireUniverse(universe); + /* create crossfilter */ + const crossfilter = Crossfilter(world.obsAnnotations); + /* create dimension map */ + const dimensionMap = World.createObsDimensionMap(crossfilter, world); + + return { + universe, + world, + crossfilter, + dimensionMap + }; +}; describe("createWorldFromEntireUniverse", () => { test("create from REST sample", () => { @@ -55,24 +80,14 @@ describe("createWorldFromEntireUniverse", () => { describe("createWorldFromCurrentSelection", () => { test("create from REST sample", () => { - /* create universe */ - const universe = Universe.createUniverseFromRestV02Response( - REST.config, - REST.schema, - REST.annotationsObs, - REST.annotationsVar, - REST.layoutObs - ); - /* create world */ - const originalWorld = World.createWorldFromEntireUniverse(universe); - /* create crossfilter */ - const crossfilter = Crossfilter(originalWorld.obsAnnotations); - - /* fake a selection */ - const dimensionMap = World.createObsDimensionMap( + const { + universe, + world: originalWorld, crossfilter, - originalWorld - ); + dimensionMap + } = defaultBigBang(); + + /* mock a selection */ dimensionMap[obsAnnoDimensionName("field1")].filterRange([0, 5]); dimensionMap[obsAnnoDimensionName("field3")].filterExact(false); @@ -128,3 +143,87 @@ describe("createWorldFromCurrentSelection", () => { ); }); }); + +describe("createObsDimensionMap", () => { + test("when universe eq world", () => { + /* + check for: + - creates a dimension for all obsAnnotations, PLUS X/Y layout + - check that dimension typing is sane + */ + + const { dimensionMap } = defaultBigBang(); + + const schemaByObsName = _.keyBy(REST.schema.schema.annotations.obs, "name"); + expect(dimensionMap).toBeDefined(); + REST.annotationsObs.names.forEach(name => { + const dim = dimensionMap[obsAnnoDimensionName(name)]; + const { type } = schemaByObsName[name]; + if (type === "string" || type === "boolean" || type === "categorical") { + expect(dim).toBeInstanceOf(Crossfilter.EnumDimension); + } else { + expect(dim).toBeInstanceOf(Crossfilter.ScalarDimension); + } + }); + expect(dimensionMap[layoutDimensionName("X")]).toBeInstanceOf( + Crossfilter.ScalarDimension + ); + expect(dimensionMap[layoutDimensionName("Y")]).toBeInstanceOf( + Crossfilter.ScalarDimension + ); + }); +}); + +describe("subsetVarData", () => { + test("when world eq universe", () => { + const { universe, world } = defaultBigBang(); + /* create a mock varData array for subsetting */ + const sourceVarData = new Float32Array(universe.nObs); + + /* expect literally the same object back */ + const result = World.subsetVarData(world, universe, sourceVarData); + expect(result).toBe(sourceVarData); + }); + + test("when world neq universe", () => { + const { universe, world, crossfilter, dimensionMap } = defaultBigBang(); + /* create a mock varData array for subsetting */ + const sourceVarData = Float32Array.from(_.range(universe.nObs)); + + /* mock a selection */ + dimensionMap[obsAnnoDimensionName("field1")].filterRange([0, 5]); + dimensionMap[obsAnnoDimensionName("field3")].filterExact(false); + + /* create the world from the selection */ + const newWorld = World.createWorldFromCurrentSelection( + universe, + world, + crossfilter + ); + + /* expect a subset */ + const result = World.subsetVarData(newWorld, universe, sourceVarData); + expect(result).not.toBe(sourceVarData); + expect(result).toHaveLength(newWorld.nObs); + /* check that we have expected source var content */ + expect(result).toMatchObject(new Float32Array([0, 2])); + }); +}); + +describe("createVarDimension", () => { + /* create default universe */ + const { world, crossfilter } = defaultBigBang(); + /* create a mock var data cache */ + const varDataCache = kvCache.set( + kvCache.create(), + "GENE", + Float32Array.from(_.range(world.nObs)) + ); + const result = World.createVarDimension( + world, + varDataCache, + crossfilter, + "GENE" + ); + expect(result).toBeInstanceOf(Crossfilter.ScalarDimension); +}); diff --git a/client/src/util/nameCreators.js b/client/src/util/nameCreators.js index e7ec1612..a4237c58 100644 --- a/client/src/util/nameCreators.js +++ b/client/src/util/nameCreators.js @@ -37,6 +37,8 @@ export const makeContinuousDimensionName = (continuousNamespace, key) => { name = diffexpDimensionName(key); } else if (continuousNamespace.isUserDefined) { name = userDefinedDimensionName(key); + } else { + throw new Error("unknown continuous dimension"); } return name;