as-test 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -5
- package/README.md +150 -149
- package/as-test.config.schema.json +24 -0
- package/assembly/coverage.ts +12 -0
- package/assembly/index.ts +16 -0
- package/assembly/src/suite.ts +2 -0
- package/assembly/test.ts +85 -0
- package/bin/commands/run-core.js +63 -4
- package/bin/commands/run.js +3 -1
- package/bin/commands/test.js +3 -1
- package/bin/coverage-points.js +207 -2
- package/bin/index.js +56 -2
- package/bin/reporters/default.js +120 -31
- package/bin/types.js +2 -0
- package/bin/util.js +24 -1
- package/package.json +3 -2
- package/transform/lib/coverage.js +220 -71
package/bin/reporters/default.js
CHANGED
|
@@ -3,7 +3,7 @@ import { diff } from "typer-diff";
|
|
|
3
3
|
import { readFileSync } from "fs";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import { formatSpecDisplayPath, formatTime } from "../util.js";
|
|
6
|
-
import { describeCoveragePoint
|
|
6
|
+
import { describeCoveragePoint } from "../coverage-points.js";
|
|
7
7
|
export const createReporter = (context) => {
|
|
8
8
|
return new DefaultReporter(context);
|
|
9
9
|
};
|
|
@@ -288,7 +288,7 @@ class DefaultReporter {
|
|
|
288
288
|
if (event.coverageSummary.enabled) {
|
|
289
289
|
renderCoverageSummary(event.coverageSummary, event.showCoverage);
|
|
290
290
|
if (event.showCoverage && event.coverageSummary.uncovered) {
|
|
291
|
-
renderCoveragePoints(event.coverageSummary.files);
|
|
291
|
+
renderCoveragePoints(event.coverageSummary.files, Boolean(event.verbose || event.showCoverageAll));
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
renderTotals(event.stats, event);
|
|
@@ -831,7 +831,7 @@ function renderCoverageSummary(summary, showCoverage) {
|
|
|
831
831
|
console.log(chalk.dim(` ... ${ranked.length - 8} more files`));
|
|
832
832
|
}
|
|
833
833
|
}
|
|
834
|
-
function renderCoveragePoints(files) {
|
|
834
|
+
function renderCoveragePoints(files, expandNested) {
|
|
835
835
|
console.log("");
|
|
836
836
|
console.log(chalk.bold("Coverage Gaps"));
|
|
837
837
|
const sortedFiles = [...files].sort((a, b) => a.file.localeCompare(b.file));
|
|
@@ -842,28 +842,124 @@ function renderCoveragePoints(files) {
|
|
|
842
842
|
displayType: describeCoveragePoint(point.file, point.line, point.column, point.type).displayType,
|
|
843
843
|
})));
|
|
844
844
|
const layout = createCoverageGapLayout(missingPoints);
|
|
845
|
+
let renderedFileCount = 0;
|
|
846
|
+
let collapsedNestedPoints = 0;
|
|
845
847
|
for (const file of sortedFiles) {
|
|
846
|
-
const points = [...file.points].sort(
|
|
847
|
-
if (a.line != b.line)
|
|
848
|
-
return a.line - b.line;
|
|
849
|
-
if (a.column != b.column)
|
|
850
|
-
return a.column - b.column;
|
|
851
|
-
return a.type.localeCompare(b.type);
|
|
852
|
-
});
|
|
848
|
+
const points = [...file.points].sort(compareCoverageGapPoints);
|
|
853
849
|
const missing = points.filter((point) => !point.executed);
|
|
854
850
|
if (!missing.length)
|
|
855
851
|
continue;
|
|
852
|
+
if (renderedFileCount > 0) {
|
|
853
|
+
console.log("");
|
|
854
|
+
}
|
|
856
855
|
console.log(` ${chalk.bold(toRelativeResultPath(file.file))} ${chalk.dim(`(${missing.length} uncovered)`)}`);
|
|
856
|
+
const pointsByHash = new Map(points.map((point) => [point.hash, point]));
|
|
857
|
+
const childrenByParent = new Map();
|
|
858
|
+
const roots = [];
|
|
857
859
|
for (const point of points) {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
860
|
+
const parentHash = point.parentHash ?? "";
|
|
861
|
+
if (parentHash.length && pointsByHash.has(parentHash)) {
|
|
862
|
+
const children = childrenByParent.get(parentHash) ?? [];
|
|
863
|
+
children.push(point);
|
|
864
|
+
childrenByParent.set(parentHash, children);
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
roots.push(point);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
const visibleRoots = roots.filter((point) => shouldRenderCoveragePoint(point, childrenByParent));
|
|
871
|
+
for (let i = 0; i < visibleRoots.length; i++) {
|
|
872
|
+
collapsedNestedPoints += renderCoveragePointTree(visibleRoots[i], childrenByParent, layout, [], i == visibleRoots.length - 1, expandNested);
|
|
865
873
|
}
|
|
874
|
+
renderedFileCount++;
|
|
866
875
|
}
|
|
876
|
+
if (!expandNested && collapsedNestedPoints > 0) {
|
|
877
|
+
console.log("");
|
|
878
|
+
console.log(chalk.dim(" Run with --show-coverage=all or --verbose to expand nested coverage gaps."));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function renderCoveragePointTree(point, childrenByParent, layout, ancestorHasNext, isLast, expandNested) {
|
|
882
|
+
const visibleChildren = [...(childrenByParent.get(point.hash) ?? [])]
|
|
883
|
+
.filter((child) => shouldRenderCoveragePoint(child, childrenByParent))
|
|
884
|
+
.sort(compareCoverageGapPoints);
|
|
885
|
+
const nestedUncoveredCount = countNestedUncoveredPoints(visibleChildren, childrenByParent);
|
|
886
|
+
if (!point.executed) {
|
|
887
|
+
renderCoverageGapLine(point, layout, ancestorHasNext, isLast);
|
|
888
|
+
if (nestedUncoveredCount > 0) {
|
|
889
|
+
if (expandNested) {
|
|
890
|
+
let rendered = 0;
|
|
891
|
+
for (let i = 0; i < visibleChildren.length; i++) {
|
|
892
|
+
rendered += renderCoveragePointTree(visibleChildren[i], childrenByParent, layout, [...ancestorHasNext, !isLast], i == visibleChildren.length - 1, expandNested);
|
|
893
|
+
}
|
|
894
|
+
return 1 + rendered;
|
|
895
|
+
}
|
|
896
|
+
const treePrefix = buildCoverageTreePrefix([...ancestorHasNext, !isLast], true);
|
|
897
|
+
console.log(` ${treePrefix}${chalk.dim(`(+${nestedUncoveredCount} nested uncovered point${nestedUncoveredCount == 1 ? "" : "s"})`)}`);
|
|
898
|
+
return nestedUncoveredCount;
|
|
899
|
+
}
|
|
900
|
+
return 0;
|
|
901
|
+
}
|
|
902
|
+
if (nestedUncoveredCount <= 0)
|
|
903
|
+
return 0;
|
|
904
|
+
renderCoverageScopeHeader(point, layout, ancestorHasNext, isLast);
|
|
905
|
+
let rendered = 0;
|
|
906
|
+
for (let i = 0; i < visibleChildren.length; i++) {
|
|
907
|
+
rendered += renderCoveragePointTree(visibleChildren[i], childrenByParent, layout, [...ancestorHasNext, !isLast], i == visibleChildren.length - 1, expandNested);
|
|
908
|
+
}
|
|
909
|
+
return rendered;
|
|
910
|
+
}
|
|
911
|
+
function shouldRenderCoveragePoint(point, childrenByParent) {
|
|
912
|
+
if (!point.executed)
|
|
913
|
+
return true;
|
|
914
|
+
return (countNestedUncoveredPoints(childrenByParent.get(point.hash) ?? [], childrenByParent) > 0);
|
|
915
|
+
}
|
|
916
|
+
function countNestedUncoveredPoints(points, childrenByParent) {
|
|
917
|
+
let count = 0;
|
|
918
|
+
for (const point of points) {
|
|
919
|
+
if (!point.executed)
|
|
920
|
+
count++;
|
|
921
|
+
count += countNestedUncoveredPoints(childrenByParent.get(point.hash) ?? [], childrenByParent);
|
|
922
|
+
}
|
|
923
|
+
return count;
|
|
924
|
+
}
|
|
925
|
+
function renderCoverageGapLine(point, layout, ancestorHasNext, isLast) {
|
|
926
|
+
const location = `${toRelativeResultPath(point.file)}:${point.line}:${point.column}`;
|
|
927
|
+
const snippet = formatCoverageSnippet(point.file, point.line, point.column, point.type, ancestorHasNext.length);
|
|
928
|
+
const typeLabel = describeCoveragePoint(point.file, point.line, point.column, point.type).displayType.padEnd(layout.typeWidth + 6);
|
|
929
|
+
const locationLabel = location.padEnd(layout.locationWidth + 6);
|
|
930
|
+
const treePrefix = buildCoverageTreePrefix(ancestorHasNext, isLast);
|
|
931
|
+
const meta = `${typeLabel}${locationLabel}`;
|
|
932
|
+
console.log(` ${treePrefix}${chalk.dim(meta)} ${snippet}`);
|
|
933
|
+
}
|
|
934
|
+
function renderCoverageScopeHeader(point, layout, ancestorHasNext, isLast) {
|
|
935
|
+
const descriptor = describeCoveragePoint(point.file, point.line, point.column, point.type);
|
|
936
|
+
const label = point.scopeKind || descriptor.displayType;
|
|
937
|
+
const location = `${toRelativeResultPath(point.file)}:${point.line}:${point.column}`;
|
|
938
|
+
const locationLabel = location.padEnd(layout.locationWidth + 6);
|
|
939
|
+
const typeLabel = label.padEnd(layout.typeWidth + 6);
|
|
940
|
+
const snippet = formatCoverageSnippet(point.file, point.line, point.column, point.type, ancestorHasNext.length);
|
|
941
|
+
const treePrefix = buildCoverageTreePrefix(ancestorHasNext, isLast);
|
|
942
|
+
const meta = `${typeLabel}${locationLabel}`;
|
|
943
|
+
console.log(` ${treePrefix}${chalk.dim(meta)} ${chalk.dim(snippet)}`);
|
|
944
|
+
}
|
|
945
|
+
function buildCoverageTreePrefix(ancestorHasNext, isLast) {
|
|
946
|
+
let out = "";
|
|
947
|
+
for (const hasNext of ancestorHasNext) {
|
|
948
|
+
out += hasNext ? "│ " : " ";
|
|
949
|
+
}
|
|
950
|
+
out += isLast ? "└─" : "├─";
|
|
951
|
+
return chalk.dim(out);
|
|
952
|
+
}
|
|
953
|
+
function compareCoverageGapPoints(a, b) {
|
|
954
|
+
if (a.line != b.line)
|
|
955
|
+
return a.line - b.line;
|
|
956
|
+
if (a.column != b.column)
|
|
957
|
+
return a.column - b.column;
|
|
958
|
+
if ((a.depth ?? 0) != (b.depth ?? 0))
|
|
959
|
+
return (a.depth ?? 0) - (b.depth ?? 0);
|
|
960
|
+
if (a.type != b.type)
|
|
961
|
+
return a.type.localeCompare(b.type);
|
|
962
|
+
return a.hash.localeCompare(b.hash);
|
|
867
963
|
}
|
|
868
964
|
function renderCoverageBar(percent) {
|
|
869
965
|
const slots = 12;
|
|
@@ -877,32 +973,25 @@ function createCoverageGapLayout(points) {
|
|
|
877
973
|
.length), 1),
|
|
878
974
|
};
|
|
879
975
|
}
|
|
880
|
-
function formatCoverageSnippet(file, line, column) {
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
return "";
|
|
884
|
-
const expanded = sourceLine.replace(/\t/g, " ");
|
|
885
|
-
const firstNonWhitespace = expanded.search(/\S/);
|
|
886
|
-
if (firstNonWhitespace == -1)
|
|
887
|
-
return "";
|
|
888
|
-
const visible = expanded.slice(firstNonWhitespace).trimEnd();
|
|
976
|
+
function formatCoverageSnippet(file, line, column, fallbackType, _depth) {
|
|
977
|
+
const descriptor = describeCoveragePoint(file, line, column, fallbackType);
|
|
978
|
+
const visible = descriptor.visible;
|
|
889
979
|
if (!visible.length)
|
|
890
980
|
return "";
|
|
891
981
|
const maxWidth = 72;
|
|
892
|
-
const focus = Math.max(0, Math.min(visible.length - 1,
|
|
982
|
+
const focus = Math.max(0, Math.min(visible.length - 1, descriptor.focus));
|
|
893
983
|
if (visible.length <= maxWidth) {
|
|
894
|
-
return styleCoverageSnippetWindow(visible, 0, visible.length, focus);
|
|
984
|
+
return styleCoverageSnippetWindow(visible, 0, visible.length, focus, descriptor.highlightStart, descriptor.highlightEnd);
|
|
895
985
|
}
|
|
896
986
|
const start = Math.max(0, Math.min(visible.length - maxWidth, focus - Math.floor(maxWidth / 2)));
|
|
897
987
|
const end = Math.min(visible.length, start + maxWidth);
|
|
898
|
-
return styleCoverageSnippetWindow(visible, start, end, focus);
|
|
988
|
+
return styleCoverageSnippetWindow(visible, start, end, focus, descriptor.highlightStart, descriptor.highlightEnd);
|
|
899
989
|
}
|
|
900
|
-
function styleCoverageSnippetWindow(visible, start, end, focus) {
|
|
990
|
+
function styleCoverageSnippetWindow(visible, start, end, focus, highlightStart, highlightEnd) {
|
|
901
991
|
const prefix = start > 0 ? "..." : "";
|
|
902
992
|
const suffix = end < visible.length ? "..." : "";
|
|
903
993
|
const slice = visible.slice(start, end);
|
|
904
994
|
const localFocus = Math.max(0, Math.min(slice.length - 1, focus - start));
|
|
905
|
-
const [highlightStart, highlightEnd] = resolveCoverageHighlightSpan(visible, focus);
|
|
906
995
|
const localStart = Math.max(0, Math.min(slice.length, highlightStart - start));
|
|
907
996
|
const localEnd = Math.max(localStart + 1, Math.min(slice.length, highlightEnd - start));
|
|
908
997
|
if (!slice.length)
|
package/bin/types.js
CHANGED
|
@@ -18,7 +18,9 @@ export class Config {
|
|
|
18
18
|
export class CoverageOptions {
|
|
19
19
|
constructor() {
|
|
20
20
|
this.enabled = false;
|
|
21
|
+
this.mode = "project";
|
|
21
22
|
this.includeSpecs = false;
|
|
23
|
+
this.dependencies = [];
|
|
22
24
|
this.include = [];
|
|
23
25
|
this.exclude = [];
|
|
24
26
|
this.ignore = new CoverageIgnoreOptions();
|
package/bin/util.js
CHANGED
|
@@ -332,7 +332,7 @@ function validateCoverageValue(value, path, issues) {
|
|
|
332
332
|
issues.push({
|
|
333
333
|
path,
|
|
334
334
|
message: "must be a boolean or object",
|
|
335
|
-
fix: 'use true/false or { "enabled": true, "includeSpecs": false, "include": ["assembly/**/*.ts"], "exclude": ["assembly/__tests__/**/*.spec.ts"] }',
|
|
335
|
+
fix: 'use true/false or { "enabled": true, "mode": "project", "includeSpecs": false, "dependencies": ["json-as"], "include": ["assembly/**/*.ts"], "exclude": ["assembly/__tests__/**/*.spec.ts"] }',
|
|
336
336
|
});
|
|
337
337
|
return;
|
|
338
338
|
}
|
|
@@ -344,6 +344,22 @@ function validateCoverageValue(value, path, issues) {
|
|
|
344
344
|
fix: "set to true or false",
|
|
345
345
|
});
|
|
346
346
|
}
|
|
347
|
+
if ("mode" in obj && obj.mode != undefined) {
|
|
348
|
+
if (typeof obj.mode != "string") {
|
|
349
|
+
issues.push({
|
|
350
|
+
path: `${path}.mode`,
|
|
351
|
+
message: 'must be "project" or "all"',
|
|
352
|
+
fix: 'set "mode" to "project" or "all"',
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
else if (obj.mode != "project" && obj.mode != "all") {
|
|
356
|
+
issues.push({
|
|
357
|
+
path: `${path}.mode`,
|
|
358
|
+
message: 'must be "project" or "all"',
|
|
359
|
+
fix: 'set "mode" to "project" or "all"',
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
347
363
|
if ("includeSpecs" in obj && typeof obj.includeSpecs != "boolean") {
|
|
348
364
|
issues.push({
|
|
349
365
|
path: `${path}.includeSpecs`,
|
|
@@ -351,6 +367,7 @@ function validateCoverageValue(value, path, issues) {
|
|
|
351
367
|
fix: "set to true or false",
|
|
352
368
|
});
|
|
353
369
|
}
|
|
370
|
+
validateStringArrayField(obj, "dependencies", path, issues);
|
|
354
371
|
validateStringArrayField(obj, "include", path, issues);
|
|
355
372
|
validateStringArrayField(obj, "exclude", path, issues);
|
|
356
373
|
if ("ignore" in obj && obj.ignore != undefined) {
|
|
@@ -929,6 +946,8 @@ function cloneCoverageOptions(coverage) {
|
|
|
929
946
|
if (typeof coverage == "boolean")
|
|
930
947
|
return coverage;
|
|
931
948
|
const cloned = Object.assign(new CoverageOptions(), coverage);
|
|
949
|
+
cloned.mode = coverage.mode ?? "project";
|
|
950
|
+
cloned.dependencies = [...(coverage.dependencies ?? [])];
|
|
932
951
|
cloned.include = [...(coverage.include ?? [])];
|
|
933
952
|
cloned.exclude = [...(coverage.exclude ?? [])];
|
|
934
953
|
cloned.ignore = Object.assign(new CoverageIgnoreOptions(), coverage.ignore);
|
|
@@ -1035,8 +1054,12 @@ function mergeCoverageConfig(base, override, raw) {
|
|
|
1035
1054
|
const rawObject = raw;
|
|
1036
1055
|
if ("enabled" in rawObject)
|
|
1037
1056
|
mergedBase.enabled = overrideOptions.enabled;
|
|
1057
|
+
if ("mode" in rawObject)
|
|
1058
|
+
mergedBase.mode = overrideOptions.mode;
|
|
1038
1059
|
if ("includeSpecs" in rawObject)
|
|
1039
1060
|
mergedBase.includeSpecs = overrideOptions.includeSpecs;
|
|
1061
|
+
if ("dependencies" in rawObject)
|
|
1062
|
+
mergedBase.dependencies = [...overrideOptions.dependencies];
|
|
1040
1063
|
if ("include" in rawObject)
|
|
1041
1064
|
mergedBase.include = [...overrideOptions.include];
|
|
1042
1065
|
if ("exclude" in rawObject)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "as-test",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"author": "Jairus Tanaka",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"as-test": "./",
|
|
21
21
|
"assemblyscript": "^0.28.17",
|
|
22
22
|
"assemblyscript-prettier": "^3.0.4",
|
|
23
|
+
"json-as": "^1.3.5",
|
|
23
24
|
"prettier": "3.8.3",
|
|
24
25
|
"try-as": "^1.0.1",
|
|
25
26
|
"typescript": "^6.0.3",
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
"docs:preview": "vitepress preview docs",
|
|
94
95
|
"format": "prettier -w .",
|
|
95
96
|
"release:check": "npm run build:cli && npm run build:lib && npm run build:transform && npm run test && npm run test:integration && npm run test:examples && npm pack --dry-run --cache /tmp/as-test-npm-cache",
|
|
96
|
-
"prepublishOnly": "npm run build:cli && npm run build:lib && npm run build:transform && npm run test && npm run test:integration"
|
|
97
|
+
"prepublishOnly": "npm run build:cli && npm run build:lib && npm run build:transform && npm run format && npm run test && npm run test:integration"
|
|
97
98
|
},
|
|
98
99
|
"type": "module"
|
|
99
100
|
}
|