Skip to content

Commit

Permalink
Test: TypeScript type specification strength tests
Browse files Browse the repository at this point in the history
  • Loading branch information
monfera committed Mar 11, 2019
1 parent 8b99d78 commit 819664f
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 52 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"test:ui:runner": "node scripts/functional_test_runner",
"test:server": "grunt test:server",
"test:coverage": "grunt test:coverage",
"typespec": "typings-tester --config x-pack/plugins/canvas/public/lib/aeroelastic/tsconfig.json x-pack/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts",
"checkLicenses": "grunt licenses --dev",
"build": "node scripts/build --all-platforms",
"start": "node --trace-warnings --trace-deprecation scripts/kibana --dev ",
Expand Down Expand Up @@ -410,6 +411,7 @@
"tslint-microsoft-contrib": "^6.0.0",
"tslint-plugin-prettier": "^2.0.0",
"typescript": "^3.3.3333",
"typings-tester": "^0.3.2",
"vinyl-fs": "^3.0.2",
"xml2js": "^0.4.19",
"xmlbuilder": "9.0.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { select } from '../../select';
import { Json, Selector } from '../..';

/*
Type checking isn't too useful if future commits can accidentally weaken the type constraints, because a
TypeScript linter will not complain - everything that passed before will continue to pass. The coder
will not have feedback that the original intent with the typing got compromised. To declare the intent
via passing and failing type checks, test cases are needed, some of which designed to expect a TS pass,
some of them to expect a TS complaint. It documents intent for peers too, as type specs are a tough read.
Run compile-time type specification tests in the `kibana` root with:
yarn typespec
Test "cases" expecting to pass TS checks are not annotated, while ones we want TS to complain about
are prepended with the comment
// typings:expect-error
The test "suite" and "cases" are wrapped in IIFEs to prevent linters from complaining about the unused
binding. It can be structured internally as desired.
*/

((): void => {
/**
* TYPE TEST SUITE
*/

(function jsonTests(plain: Json): void {
// numbers are OK
plain = 1;
plain = NaN;
plain = Infinity;
plain = -Infinity;
plain = Math.pow(2, 6);
// other JSON primitive types are OK
plain = false;
plain = 'hello';
plain = null;
// structures made of above and of structures are OK
plain = {};
plain = [];
plain = { a: 1 };
plain = [0, null, false, NaN, 3.14, 'one more'];
plain = { a: { b: 5, c: { d: [1, 'a', -Infinity, null], e: -1 }, f: 'b' }, g: false };

// typings:expect-error
plain = undefined; // it's undefined
// typings:expect-error
plain = a => a; // it's a function
// typings:expect-error
plain = [new Date()]; // it's a time
// typings:expect-error
plain = { a: Symbol('haha') }; // symbol isn't permitted either
// typings:expect-error
plain = window || void 0;
// typings:expect-error
plain = { a: { b: 5, c: { d: [1, 'a', undefined, null] } } }; // going deep into the structure

return; // jsonTests
})(null);

(function selectTests(selector: Selector): void {
selector = select((a: Json) => a); // one arg
selector = select((a: Json, b: Json): Json => `${a} and ${b}`); // more args
selector = select(() => 1); // zero arg
selector = select((...args: Json[]) => args); // variadic

// typings:expect-error
selector = (a: Json) => a; // not a selector
// typings:expect-error
selector = select(() => {}); // should yield a JSON value, but it returns void
// typings:expect-error
selector = select((x: Json) => ({ a: x, b: undefined })); // should return a Json

return; // selectTests
})(select((a: Json) => a));

return; // test suite
})();
2 changes: 1 addition & 1 deletion x-pack/plugins/canvas/public/lib/aeroelastic/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { transformMatrix3d } from './types';
import { transformMatrix3d } from '.';

// converts a transform matrix to a CSS string
export const matrixToCSS = (transformMatrix: transformMatrix3d): string =>
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/canvas/public/lib/aeroelastic/gestures.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { select, selectReduce } from './state';
import { select, selectReduce } from './select';

// Only needed to shuffle some modifier keys for Apple keyboards as per vector editing software conventions,
// so it's OK that user agent strings are not reliable; in case it's spoofed, it'll just work with a slightly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

// linear algebra
type f64 = number; // eventual AssemblyScript compatibility; doesn't hurt with vanilla TS either
type f = f64; // shorthand

Expand All @@ -15,16 +16,36 @@ export type transformMatrix2d = [f, f, f, f, f, f, f, f, f] &
export type transformMatrix3d = [f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f] &
ReadonlyArray<f> & { __nominal: 'transformMatrix3d' };

// plain, JSON-bijective value
export type Json = JsonPrimitive | JsonArray | JsonMap;
type JsonPrimitive = null | boolean | number | string;
interface JsonArray extends Array<Json> {}
interface JsonMap extends IMap<Json> {}
interface IMap<T> {
[key: string]: T;
}

// state object
export type State = JsonMap & WithActionId;
export type ActionId = number;
interface WithActionId {
primaryUpdate: { type: string; payload: { uid: ActionId; [propName: string]: Json } };
[propName: string]: Json; // allow other arbitrary props
}

// reselect-based data flow
export type PlainFun = (...args: Json[]) => Json;
export type Selector = (...fns: Resolve[]) => Resolve;
type Resolve = ((obj: State) => Json);

//
export interface Meta {
silent: boolean;
}
export type ActionId = number;
export type TypeName = string;
export type NodeResult = any;
export type Payload = any;
export type NodeFunction = (...args: any[]) => any;
export type UpdaterFunction = (arg: NodeResult) => NodeResult;
export type Payload = JsonMap;
export type UpdaterFunction = (arg: State) => State;
export type ChangeCallbackFunction = (
{ type, state }: { type: TypeName; state: NodeResult },
{ type, state }: { type: TypeName; state: State },
meta: Meta
) => void;
2 changes: 1 addition & 1 deletion x-pack/plugins/canvas/public/lib/aeroelastic/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { matrixToCSS } from './dom';
import { nextScene } from './layout';
import { primaryUpdate } from './layout_functions';
import { multiply, rotateZ, translate } from './matrix';
import { createStore, select } from './state';
import { createStore, select } from './select';

export const layout = { nextScene, primaryUpdate };
export const matrix = { multiply, rotateZ, translate };
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/canvas/public/lib/aeroelastic/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { select } from './state';
import { select } from './select';

import {
actionEvent,
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/canvas/public/lib/aeroelastic/matrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*
*/

import { transformMatrix3d, vector3d } from './types';
import { transformMatrix3d, vector3d } from '.';

const NANMATRIX = [
NaN,
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/canvas/public/lib/aeroelastic/matrix2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { transformMatrix2d, vector2d } from './types';
import { transformMatrix2d, vector2d } from '.';

export const ORIGIN = [0, 0, 1] as vector2d;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import {
ActionId,
ChangeCallbackFunction,
Json,
Meta,
NodeFunction,
NodeResult,
Payload,
PlainFun,
Selector,
State,
TypeName,
UpdaterFunction,
} from './types';
} from './index';

export const shallowEqual = (a: any, b: any): boolean => {
if (a === b) {
Expand All @@ -32,15 +34,20 @@ export const shallowEqual = (a: any, b: any): boolean => {

const makeUid = (): ActionId => 1e11 + Math.floor((1e12 - 1e11) * Math.random());

export const selectReduce = (fun: NodeFunction, previousValue: NodeResult): NodeFunction => (
...inputs: NodeFunction[]
): NodeResult => {
export const select = (fun: PlainFun): Selector => (...fns) => {
let { prevId, cache } = { prevId: NaN as ActionId, cache: null as Json };
const old = (object: State): boolean => prevId === (prevId = object.primaryUpdate.payload.uid);
return obj => (old(obj) ? cache : (cache = fun(...fns.map(f => f(obj) as Json))));
};

// this function `selectReduce` is in the process of being removed in another PR
export const selectReduce = (fun: PlainFun, previousValue: Json): Selector => (...inputs) => {
// last-value memoizing version of this single line function:
// (fun, previousValue) => (...inputs) => state => previousValue = fun(previousValue, ...inputs.map(input => input(state)))
let argumentValues = [] as NodeResult[];
let argumentValues = [] as Json[];
let value = previousValue;
let prevValue = previousValue;
return (state: NodeResult) => {
return (state: State) => {
if (
shallowEqual(argumentValues, (argumentValues = inputs.map(input => input(state)))) &&
value === prevValue
Expand All @@ -54,32 +61,10 @@ export const selectReduce = (fun: NodeFunction, previousValue: NodeResult): Node
};
};

export const select = (fun: NodeFunction): NodeFunction => (
...inputs: NodeFunction[]
): NodeResult => {
// last-value memoizing version of this single line function:
// fun => (...inputs) => state => fun(...inputs.map(input => input(state)))
let argumentValues = [] as NodeResult[];
let value: NodeResult;
let actionId: ActionId;
return (state: NodeResult) => {
const lastActionId: ActionId = state.primaryUpdate.payload.uid;
if (
actionId === lastActionId ||
shallowEqual(argumentValues, (argumentValues = inputs.map(input => input(state))))
) {
return value;
}

value = fun(...argumentValues);
actionId = lastActionId;
return value;
};
};

export const createStore = (initialState: NodeResult, onChangeCallback: ChangeCallbackFunction) => {
// this function `createStore` is in the process of being removed in another PR
export const createStore = (initialState: State, onChangeCallback: ChangeCallbackFunction) => {
let currentState = initialState;
let updater: UpdaterFunction = (state: NodeResult): NodeResult => state; // default: no side effect
let updater: UpdaterFunction = (state: State): State => state; // default: no side effect
const getCurrentState = () => currentState;
// const setCurrentState = newState => (currentState = newState);
const setUpdater = (updaterFunction: UpdaterFunction) => {
Expand Down
21 changes: 21 additions & 0 deletions x-pack/plugins/canvas/public/lib/aeroelastic/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"extends": "../../../../../tsconfig",
"compilerOptions": {
"module": "commonjs",
"lib": ["esnext", "dom"],
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": false,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"layout/*": ["aeroelastic/*"]
},
"types": ["@kbn/x-pack/plugins/canvas/public/lib/aeroelastic"]
},
"exclude": ["node_modules", "**/*.spec.ts", "node_modules/@types/mocha"]
}
3 changes: 2 additions & 1 deletion x-pack/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"test_utils/**/*"
],
"exclude": [
"test/**/*"
"test/**/*",
"**/typespec_tests.ts"
],
"compilerOptions": {
"outDir": ".",
Expand Down
14 changes: 8 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6687,7 +6687,7 @@ [email protected]:
resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06"
integrity sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=

commander@2, [email protected], commander@^2.11.0, commander@^2.12.1, commander@^2.19.0, commander@^2.9.0:
commander@2, [email protected], commander@^2.11.0, commander@^2.12.1, commander@^2.12.2, commander@^2.19.0, commander@^2.9.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
Expand Down Expand Up @@ -23580,12 +23580,14 @@ typescript@^3.3.3333:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3333.tgz#171b2c5af66c59e9431199117a3bcadc66fdcfd6"
integrity sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==

ua-parser-js@^0.7.18:
version "0.7.18"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed"
integrity sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==
typings-tester@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/typings-tester/-/typings-tester-0.3.2.tgz#04cc499d15ab1d8b2d14dd48415a13d01333bc5b"
integrity sha512-HjGoAM2UoGhmSKKy23TYEKkxlphdJFdix5VvqWFLzH1BJVnnwG38tpC6SXPgqhfFGfHY77RlN1K8ts0dbWBQ7A==
dependencies:
commander "^2.12.2"

ua-parser-js@^0.7.9:
ua-parser-js@^0.7.18, ua-parser-js@^0.7.9:
version "0.7.19"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b"
integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==
Expand Down

0 comments on commit 819664f

Please sign in to comment.