Skip to content

Commit

Permalink
test(unit): Add test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
James Zetlen committed Dec 2, 2019
1 parent 4afa27f commit 2c0adef
Show file tree
Hide file tree
Showing 18 changed files with 832 additions and 92 deletions.
2 changes: 0 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ jobs:
'node-10':
docker:
- image: circleci/node:10
working_directory: ~/typescript-starter
steps:
- checkout
# Download and cache dependencies
Expand All @@ -24,7 +23,6 @@ jobs:
'node-latest':
docker:
- image: circleci/node:latest
working_directory: ~/typescript-starter
steps:
- checkout
- restore_cache:
Expand Down
16 changes: 13 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"version": "0.2.0",
"configurations": [{
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Project",
Expand Down Expand Up @@ -35,5 +36,14 @@
// "preLaunchTask": "npm: build",
// "smartStep": true,
"runtimeArgs": ["--nolazy"]
}]
}
},
{
"type": "node",
"request": "launch",
"name": "Debug File",
"program": "${workspaceFolder}/node_modules/ava/profile.js",
"args": ["${file}"],
"skipFiles": ["<node_internals>/**/*.js"]
}
]
}
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"coverage-gutters.coverageReportFileName": "coverage/index.html"
// "typescript.implementationsCodeLens.enabled": true
// "typescript.referencesCodeLens.enabled": true
}
25 changes: 20 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@
"test": "run-s build test:*",
"test:lint": "tslint --project . && prettier \"src/**/*.ts\" --list-different",
"test:unit": "nyc --silent ava",
"watch": "run-s clean build:main && run-p \"build:main -- -w\" \"test:unit -- --watch\"",
"watch": "run-s \"test:unit -- --watch\"",
"cov": "run-s build test:unit cov:html && open-cli coverage/index.html",
"cov:html": "nyc report --reporter=html",
"cov:send": "nyc report --reporter=lcov && codecov",
"cov:unit": "run-s clean build:main && nyc --reporter=lcov ava",
"cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100",
"doc": "run-s doc:html && open-cli build/docs/index.html",
"doc:html": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --out build/docs",
Expand Down Expand Up @@ -84,6 +85,7 @@
"open-cli": "^5.0.0",
"prettier": "^1.18.2",
"sharp": "^0.23.3",
"source-map-support": "^0.5.16",
"standard-version": "^6.0.1",
"trash-cli": "^3.0.0",
"ts-node": "^8.5.2",
Expand All @@ -95,12 +97,23 @@
"sharp": "^0.23.3"
},
"ava": {
"compileEnhancements": false,
"extensions": [
"ts"
],
"failFast": true,
"files": [
"build/main/**/*.spec.js"
"src/**/*.spec.ts"
],
"helpers": [
"**/__*.ts"
],
"require": [
"ts-node/register",
"source-map-support/register"
],
"sources": [
"build/main/**/*.js"
"src/**/*.ts"
]
},
"config": {
Expand All @@ -113,8 +126,10 @@
},
"nyc": {
"extends": "@istanbuljs/nyc-config-typescript",
"exclude": [
"**/*.spec.js"
"include": [
"src/**/*.ts",
"!**/*.spec.ts",
"!**/__*.ts"
]
}
}
53 changes: 53 additions & 0 deletions src/lib/helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import test from 'ava';
import { FastlyParamError } from './errors';
import * as h from './helpers';
import { Param } from './imageopto-types';

test('getTaggedValues honors positionals', t => {
t.deepEqual(
h.getTaggedValues(
new Map<Param, string>([['canvas', 'a,b,ccee,ddee,feff,eeee']]),
'canvas',
['ay', 'bee'],
['c', 'd', 'e', 'f']
),
{
ay: 'a',
bee: 'b',
c: 'cee',
d: 'dee',
e: 'eee',
f: 'eff'
}
);
});

test('getTaggedValues honors positionals 2 s', t => {
t.deepEqual(
h.getTaggedValues(
new Map<Param, string>([['canvas', '300,300,x90,y10']]),
'canvas',
['width', 'height'],
['x', 'y', 'smart', 'offset-x', 'offset-y']
),
{
height: '300',
width: '300',
x: '90',
y: '10'
}
);
});

test('getTaggedValues does not put named params in positionals', t => {
t.throws(
() =>
h.getTaggedValues(
new Map<Param, string>([['canvas', '400,gutter5']]),
'canvas',
['width', 'height'],
['gutter']
),
FastlyParamError
);
});
112 changes: 67 additions & 45 deletions src/lib/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,63 +113,85 @@ export const colorFromParam = (params: Params, name: Param): Color => {
return die();
};

export const regionFromParam = (params: Params, name: Param): Region => {
export function getTaggedValues(
params: Params,
name: Param,
positional: string[],
named: string[]
): { [key: string]: string | null } {
const csv = params.get(name) as string;
const values = csv.split(',').map(v => v.toLowerCase().trim());
const tagged: { [key: string]: string | null } = {};
named.forEach(n => {
const valueIndex = values.findIndex(value => value.startsWith(n));
if (valueIndex > -1) {
tagged[n] = values[valueIndex].slice(n.length);
values.splice(valueIndex, 1);
}
});
// values should just be required positional now
if (values.length > positional.length) {
throw new FastlyParamError(params, name);
} else if (values.length < positional.length) {
throw new FastlyParamError(
params,
name,
`${positional} arguments required`
);
} else {
values.forEach((value, i) => {
tagged[positional[i]] = value;
});
}
return tagged;
}

export const regionFromParam = (
params: Params,
name: Param
): Partial<Region> => {
const csv = params.get(name) as string;
if (csv.includes(':') || csv.includes('offset')) {
throw new FastlyCompatError(params, name, 'ratio-based regions');
}
if (!csv.includes(',')) {
throw new FastlyParamError(params, name, 'width and height are required');
const values = getTaggedValues(
params,
name,
['width', 'height'],
['x', 'y', 'smart', 'offset-x', 'offset-y']
);
if (values.hasOwnProperty('smart')) {
throw new FastlyCompatError(params, name, 'smart image cropping');
}
const [width, height, ...rest] = csv.split(',');
const region = ({} as unknown) as Region;
const numWidth = Number(width);
if (isNaN(numWidth)) {
throw new FastlyParamError(params, name, 'width must be numeric');
}
region.width = numWidth;
const numHeight = Number(height);
if (isNaN(numHeight)) {
throw new FastlyParamError(params, name, 'height must be numeric');
}
region.height = numHeight;
const validateDimension = (value: string, dim: 'x' | 'y') => {
const matches = value.match(new RegExp(`^${dim}(\\d+)$`));
if (!matches) {
throw new FastlyParamError(
params,
name,
`${dim} must be "${dim}<pixel>"`
);

function validate(tag: string, optional?: true) {
if (optional && !values.hasOwnProperty(tag)) {
return;
}
return Number(matches[1]);
};
if (rest.length > 2) {
const last = rest.pop();
if (
last &&
last
.toString()
.toLowerCase()
.trim() === 'smart'
) {
const value = Number(values[tag]);
if (isNaN(value)) {
throw new FastlyParamError(params, name, `${tag} is ${values[tag]}`);
}
if (value < 1 && value !== 0) {
throw new FastlyCompatError(
params,
'smart' as Param,
'smart image cropping'
name,
`percentage ${tag} value in regions (must use absolute pixels)`
);
}
throw new FastlyParamError(
params,
name,
`unrecognized parameters: ${rest.join(', ')}`
);
return value;
}
if (rest.length === 2) {
region.top = validateDimension(rest[1], 'y');
const region: Partial<Region> = {
height: validate('height'),
width: validate('width')
};
const left = validate('x', true);
if (left !== undefined) {
region.left = left;
}
if (rest.length > 0) {
region.left = validateDimension(rest[0], 'x');
const top = validate('y', true);
if (top !== undefined) {
region.top = top;
}
return region;
};
Loading

0 comments on commit 2c0adef

Please sign in to comment.