elm-test
Version:
Run elm-test suites.
236 lines (206 loc) • 5.99 kB
JavaScript
// @flow
const fs = require('fs');
const path = require('path');
// Poor man’s type alias. We can’t use /*:: type Dependencies = ... */ because of:
// https://github.com/prettier/prettier/issues/2597
const Dependencies /*: { [string]: string } */ = {};
const DirectAndIndirectDependencies /*: {
direct: typeof Dependencies,
indirect: typeof Dependencies,
} */ = { direct: {}, indirect: {} };
const ElmJson /*:
| {
type: 'application',
'source-directories': Array<string>,
dependencies: typeof DirectAndIndirectDependencies,
'test-dependencies': typeof DirectAndIndirectDependencies,
[string]: mixed,
}
| {
type: 'package',
dependencies: typeof Dependencies,
'test-dependencies': typeof Dependencies,
[string]: mixed,
} */ = {
type: 'package',
dependencies: Dependencies,
'test-dependencies': Dependencies,
};
function getPath(dir /*: string */) /*: string */ {
return path.join(dir, 'elm.json');
}
function write(dir /*: string */, elmJson /*: typeof ElmJson */) /*: void */ {
const elmJsonPath = getPath(dir);
try {
fs.writeFileSync(elmJsonPath, JSON.stringify(elmJson, null, 4) + '\n');
} catch (error) {
throw new Error(
`${elmJsonPath}\nFailed to write elm.json:\n${error.message}`
);
}
}
function read(dir /*: string */) /*: typeof ElmJson */ {
const elmJsonPath = getPath(dir);
try {
return readHelper(elmJsonPath);
} catch (error) {
throw new Error(
`${elmJsonPath}\nFailed to read elm.json:\n${error.message}`
);
}
}
function readHelper(elmJsonPath /*: string */) /*: typeof ElmJson */ {
const json = parseObject(
JSON.parse(fs.readFileSync(elmJsonPath, 'utf8')),
'the file'
);
switch (json.type) {
case 'application':
return {
...json,
type: 'application',
'source-directories': parseSourceDirectories(
json['source-directories']
),
dependencies: parseDirectAndIndirectDependencies(
json.dependencies,
'dependencies'
),
'test-dependencies': parseDirectAndIndirectDependencies(
json['test-dependencies'],
'test-dependencies'
),
};
case 'package':
return {
...json,
type: 'package',
dependencies: parseDependencies(json.dependencies, 'dependencies'),
'test-dependencies': parseDependencies(
json['test-dependencies'],
'test-dependencies'
),
};
default:
throw new Error(
`Expected "type" to be "application" or "package", but got: ${stringify(
json.type
)}`
);
}
}
function parseSourceDirectories(json /*: mixed */) /*: Array<string> */ {
if (!Array.isArray(json)) {
throw new Error(
`Expected "source-directories" to be an array, but got: ${stringify(
json
)}`
);
}
const result = [];
for (const [index, item] of json.entries()) {
if (typeof item !== 'string') {
throw new Error(
`Expected "source-directories"->${index} to be a string, but got: ${stringify(
item
)}`
);
}
result.push(item);
}
if (result.length === 0) {
throw new Error(
'Expected "source-directories" to contain at least one item, but it is empty.'
);
}
return result;
}
function parseDirectAndIndirectDependencies(
json /*: mixed */,
what /*: string */
) /*: typeof DirectAndIndirectDependencies */ {
const jsonObject = parseObject(json, what);
return {
direct: parseDependencies(jsonObject.direct, `${what}->"direct"`),
indirect: parseDependencies(jsonObject.indirect, `${what}->"indirect"`),
};
}
function parseDependencies(
json /*: mixed */,
what /*: string */
) /*: typeof Dependencies */ {
const jsonObject = parseObject(json, what);
const result = {};
for (const [key, value] of Object.entries(jsonObject)) {
if (typeof value !== 'string') {
throw new Error(
`Expected ${what}->${stringify(
key
)} to be a string, but got: ${stringify(value)}`
);
}
result[key] = value;
}
return result;
}
function parseObject(
json /*: mixed */,
what /*: string */
) /*: { +[string]: mixed } */ {
if (json == null || typeof json !== 'object' || Array.isArray(json)) {
throw new Error(
`Expected ${what} to be an object, but got: ${stringify(json)}`
);
}
return json;
}
function stringify(json /*: mixed */) /*: string */ {
const maybeString = JSON.stringify(json);
return maybeString === undefined ? 'undefined' : maybeString;
}
const ELM_TEST_PACKAGE = 'elm-explorations/test';
function requireElmTestPackage(
dir /*: string */,
elmJson /*: typeof ElmJson */
) /*: void */ {
const elmJsonPath = getPath(dir);
const versionOrRange = getElmExplorationsTestPackageVersionOrRange(elmJson);
if (versionOrRange === undefined) {
throw new Error(
`${elmJsonPath}\nYou must have "${ELM_TEST_PACKAGE}" in your "test-dependencies" or "dependencies" to run elm-test.`
);
} else if (!versionOrRange.trimStart().startsWith('2.')) {
throw new Error(
`${elmJsonPath}\nThis version of elm-test only supports ${ELM_TEST_PACKAGE} 2.x, but you have ${stringify(
versionOrRange
)}.`
);
}
}
function getElmExplorationsTestPackageVersionOrRange(
elmJson /*: typeof ElmJson */
) /*: string | void */ {
switch (elmJson.type) {
case 'application':
return (
elmJson['test-dependencies'].direct[ELM_TEST_PACKAGE] ||
elmJson.dependencies.direct[ELM_TEST_PACKAGE]
);
case 'package':
return (
elmJson['test-dependencies'][ELM_TEST_PACKAGE] ||
elmJson.dependencies[ELM_TEST_PACKAGE]
);
}
}
module.exports = {
ELM_TEST_PACKAGE,
Dependencies,
DirectAndIndirectDependencies,
ElmJson,
getPath,
parseDirectAndIndirectDependencies,
read,
requireElmTestPackage,
write,
};