track-cli 4.0.3 → 4.1.0-rc1
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/esm/_dnt.shims.d.ts +1 -1
- package/esm/_dnt.test_shims.d.ts +20 -0
- package/esm/_dnt.test_shims.js +77 -0
- package/esm/deps/deno.land/std@0.195.0/_util/diff.d.ts +26 -0
- package/esm/deps/deno.land/std@0.195.0/_util/diff.js +311 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_constants.d.ts +1 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_constants.js +2 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_format.d.ts +1 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_format.js +23 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_almost_equals.d.ts +18 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_almost_equals.js +32 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_array_includes.d.ts +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_array_includes.js +38 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_equals.d.ts +17 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_equals.js +45 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_exists.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_exists.js +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_false.d.ts +4 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_false.js +7 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_instance_of.d.ts +8 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_instance_of.js +38 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_is_error.d.ts +7 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_is_error.js +26 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_match.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_match.js +13 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_equals.d.ts +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_equals.js +37 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_instance_of.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_instance_of.js +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_match.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_match.js +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_strict_equals.d.ts +11 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_strict_equals.js +20 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_object_match.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_object_match.js +78 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_rejects.d.ts +64 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_rejects.js +50 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_strict_equals.d.ts +23 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_strict_equals.js +60 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_string_includes.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_string_includes.js +13 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_throws.d.ts +54 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_throws.js +44 -0
- package/esm/deps/deno.land/std@0.195.0/assert/equal.d.ts +6 -0
- package/esm/deps/deno.land/std@0.195.0/assert/equal.js +102 -0
- package/esm/deps/deno.land/std@0.195.0/assert/fail.d.ts +4 -0
- package/esm/deps/deno.land/std@0.195.0/assert/fail.js +9 -0
- package/esm/deps/deno.land/std@0.195.0/assert/mod.d.ts +32 -0
- package/esm/deps/deno.land/std@0.195.0/assert/mod.js +33 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unimplemented.d.ts +2 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unimplemented.js +7 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unreachable.d.ts +2 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unreachable.js +6 -0
- package/esm/deps/deno.land/std@0.195.0/testing/_test_suite.d.ts +70 -0
- package/esm/deps/deno.land/std@0.195.0/testing/_test_suite.js +321 -0
- package/esm/deps/deno.land/std@0.195.0/testing/asserts.d.ts +329 -0
- package/esm/deps/deno.land/std@0.195.0/testing/asserts.js +330 -0
- package/esm/deps/deno.land/std@0.195.0/testing/bdd.d.ts +440 -0
- package/esm/deps/deno.land/std@0.195.0/testing/bdd.js +215 -0
- package/esm/deps/deno.land/std@0.195.0/testing/mock.d.ts +110 -0
- package/esm/deps/deno.land/std@0.195.0/testing/mock.js +746 -0
- package/esm/src/action/clone.js +4 -3
- package/esm/src/action/frontend-template-switch.d.ts +1 -0
- package/esm/src/action/frontend-template-switch.js +56 -0
- package/esm/src/action/frontend-template.d.ts +2 -0
- package/esm/src/action/frontend-template.js +12 -0
- package/esm/src/main.js +9 -2
- package/esm/src/meta.d.ts +1 -1
- package/esm/src/meta.js +108 -25
- package/esm/src/orca/client.js +1 -1
- package/esm/src/shared/config.d.ts +1 -0
- package/esm/src/shared/file.d.ts +1 -0
- package/esm/src/shared/file.js +32 -0
- package/esm/src/shared/mod.d.ts +1 -1
- package/esm/src/shared/mod.js +10 -2
- package/esm/src/shared/types.d.ts +15 -0
- package/esm/src/shared/types.js +1 -0
- package/esm/src/track/client.d.ts +4 -1
- package/esm/src/track/test.d.ts +5 -2
- package/esm/src/track/test.js +18 -2
- package/esm/src/track/training.d.ts +4 -1
- package/esm/src/track/training.js +9 -0
- package/esm/test/shared/config_test.d.ts +1 -0
- package/esm/test/shared/config_test.js +57 -0
- package/esm/test/shared/file_test.d.ts +1 -0
- package/esm/test/shared/file_test.js +265 -0
- package/esm/test/shared/mod_test.d.ts +1 -0
- package/esm/test/shared/mod_test.js +353 -0
- package/package.json +2 -1
- package/test_runner.js +186 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as dntShim from "../../_dnt.test_shims.js";
|
|
2
|
+
import * as config from "../../src/shared/config.js";
|
|
3
|
+
import { MissingConfigFile } from "../../src/shared/errors.js";
|
|
4
|
+
import { assertEquals, assertRejects } from "../../deps/deno.land/std@0.195.0/testing/asserts.js";
|
|
5
|
+
import { afterEach, beforeEach, describe, it } from "../../deps/deno.land/std@0.195.0/testing/bdd.js";
|
|
6
|
+
import { TrackApp } from "../../src/shared/types.js";
|
|
7
|
+
const testConfig = {
|
|
8
|
+
app: TrackApp.Test,
|
|
9
|
+
baseUrl: "https://app.tracks.run",
|
|
10
|
+
orgName: "anorg",
|
|
11
|
+
basicAuth: {
|
|
12
|
+
username: "user",
|
|
13
|
+
password: "pass",
|
|
14
|
+
},
|
|
15
|
+
token: "SECRET_TOKEN",
|
|
16
|
+
applicantExamId: 100,
|
|
17
|
+
challengeId: 200,
|
|
18
|
+
challengeResultId: 300,
|
|
19
|
+
cookies: {
|
|
20
|
+
PLAY_SESSION: "session",
|
|
21
|
+
},
|
|
22
|
+
programmingLanguage: 10,
|
|
23
|
+
};
|
|
24
|
+
describe("save()", () => {
|
|
25
|
+
let dir;
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
dir = await dntShim.Deno.makeTempDir();
|
|
28
|
+
});
|
|
29
|
+
afterEach(async () => {
|
|
30
|
+
await dntShim.Deno.remove(dir, { recursive: true });
|
|
31
|
+
});
|
|
32
|
+
it("should save config to file with json format", async () => {
|
|
33
|
+
const path = `${dir}/config.json`;
|
|
34
|
+
await config.save(testConfig, { path });
|
|
35
|
+
const actual = new TextDecoder().decode(await dntShim.Deno.readFile(path));
|
|
36
|
+
assertEquals(JSON.parse(actual), testConfig);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("load()", () => {
|
|
40
|
+
let dir;
|
|
41
|
+
beforeEach(async () => {
|
|
42
|
+
dir = await dntShim.Deno.makeTempDir();
|
|
43
|
+
});
|
|
44
|
+
afterEach(async () => {
|
|
45
|
+
await dntShim.Deno.remove(dir, { recursive: true });
|
|
46
|
+
});
|
|
47
|
+
it("should save config to file with json format", async () => {
|
|
48
|
+
const path = `${dir}/config.json`;
|
|
49
|
+
await dntShim.Deno.writeFile(path, new TextEncoder().encode(JSON.stringify(testConfig)));
|
|
50
|
+
const actual = await config.load({ path });
|
|
51
|
+
assertEquals(actual, testConfig);
|
|
52
|
+
});
|
|
53
|
+
it("should throw error if config file does not exist", async () => {
|
|
54
|
+
const path = `${dir}/config.json`;
|
|
55
|
+
await assertRejects(async () => await config.load({ path }), MissingConfigFile);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import * as dntShim from "../../_dnt.test_shims.js";
|
|
2
|
+
import * as file from "../../src/shared/file.js";
|
|
3
|
+
import { assertEquals, assertObjectMatch } from "../../deps/deno.land/std@0.195.0/testing/asserts.js";
|
|
4
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, it, } from "../../deps/deno.land/std@0.195.0/testing/bdd.js";
|
|
5
|
+
async function prepareFiles(dir, files) {
|
|
6
|
+
for (const [name, content] of Object.entries(files)) {
|
|
7
|
+
if (typeof content === "string") {
|
|
8
|
+
await dntShim.Deno.writeTextFile(`${dir}/${name}`, content);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
await dntShim.Deno.mkdir(`${dir}/${name}`);
|
|
12
|
+
await prepareFiles(`${dir}/${name}`, content);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
describe("pathsToFilenameSet()", () => {
|
|
17
|
+
let dir;
|
|
18
|
+
const files = {
|
|
19
|
+
"a.txt": "hello",
|
|
20
|
+
"b": {
|
|
21
|
+
"c.txt": "world",
|
|
22
|
+
"d": {
|
|
23
|
+
"e.txt": "foo",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
beforeAll(async () => {
|
|
28
|
+
dir = await dntShim.Deno.makeTempDir();
|
|
29
|
+
await prepareFiles(dir, files);
|
|
30
|
+
});
|
|
31
|
+
afterAll(async () => {
|
|
32
|
+
await dntShim.Deno.remove(dir, { recursive: true });
|
|
33
|
+
});
|
|
34
|
+
it("should list only existing files without options", async () => {
|
|
35
|
+
const result = await file.pathsToFilenameSet(["a.txt", "b/c.txt", "b/d", "x/y", "z.txt"], { root: dir });
|
|
36
|
+
assertEquals(result, new Set(["a.txt", "b/c.txt"]));
|
|
37
|
+
});
|
|
38
|
+
it("should search files recursively with recursive = true", async () => {
|
|
39
|
+
const result = await file.pathsToFilenameSet(["a.txt", "b", "x/y", "z.txt"], { recursive: true, root: dir });
|
|
40
|
+
assertEquals(result, new Set(["a.txt", "b/c.txt", "b/d/e.txt"]));
|
|
41
|
+
});
|
|
42
|
+
it("should search files recursively and list non-existing files and directories with recursive = true and allowNonExistent = true", async () => {
|
|
43
|
+
const result = await file.pathsToFilenameSet(["a.txt", "b", "x/y", "z.txt"], { recursive: true, allowNonExistent: true, root: dir });
|
|
44
|
+
assertEquals(result, new Set(["a.txt", "b/c.txt", "b/d/e.txt", "x/y", "z.txt"]));
|
|
45
|
+
});
|
|
46
|
+
it("should support absolute paths", async () => {
|
|
47
|
+
const result = await file.pathsToFilenameSet([`${dir}/a.txt`, `${dir}/b`, `${dir}/x/y`, `${dir}/z.txt`], { recursive: true, allowNonExistent: true, root: dir });
|
|
48
|
+
assertEquals(result, new Set(["a.txt", "b/c.txt", "b/d/e.txt", "x/y", "z.txt"]));
|
|
49
|
+
});
|
|
50
|
+
it("should support only paths with root", async () => {
|
|
51
|
+
const anotherDir = await dntShim.Deno.makeTempDir();
|
|
52
|
+
await prepareFiles(anotherDir, files);
|
|
53
|
+
const result = await file.pathsToFilenameSet([`${dir}/a.txt`, `${anotherDir}/z.txt`], { recursive: true, allowNonExistent: true, root: dir });
|
|
54
|
+
assertEquals(result, new Set(["a.txt"]));
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("stat()", () => {
|
|
58
|
+
let dir;
|
|
59
|
+
const files = {
|
|
60
|
+
"a.txt": "hello",
|
|
61
|
+
"b": {
|
|
62
|
+
"c.txt": "world",
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
beforeAll(async () => {
|
|
66
|
+
dir = await dntShim.Deno.makeTempDir();
|
|
67
|
+
await prepareFiles(dir, files);
|
|
68
|
+
});
|
|
69
|
+
afterAll(async () => {
|
|
70
|
+
await dntShim.Deno.remove(dir, { recursive: true });
|
|
71
|
+
});
|
|
72
|
+
it("should return stat of file", async () => {
|
|
73
|
+
const result = await file.stat(`${dir}/a.txt`);
|
|
74
|
+
assertObjectMatch(result, {
|
|
75
|
+
isFile: true,
|
|
76
|
+
isDirectory: false,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it("should return stat of directory", async () => {
|
|
80
|
+
const result = await file.stat(`${dir}/b`);
|
|
81
|
+
assertObjectMatch(result, {
|
|
82
|
+
isFile: false,
|
|
83
|
+
isDirectory: true,
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
it("should return null if file does not exist", async () => {
|
|
87
|
+
const result = await file.stat(`${dir}/x.txt`);
|
|
88
|
+
assertEquals(result, null);
|
|
89
|
+
});
|
|
90
|
+
it("should support subdirectories", async () => {
|
|
91
|
+
const result = await file.stat(`${dir}/b/c.txt`);
|
|
92
|
+
assertObjectMatch(result, {
|
|
93
|
+
isFile: true,
|
|
94
|
+
isDirectory: false,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe("isEmptyDirectory()", () => {
|
|
99
|
+
let dir;
|
|
100
|
+
const files = {
|
|
101
|
+
"a.txt": "hello",
|
|
102
|
+
"b": {
|
|
103
|
+
"c.txt": "world",
|
|
104
|
+
},
|
|
105
|
+
"d": {},
|
|
106
|
+
};
|
|
107
|
+
beforeAll(async () => {
|
|
108
|
+
dir = await dntShim.Deno.makeTempDir();
|
|
109
|
+
await prepareFiles(dir, files);
|
|
110
|
+
});
|
|
111
|
+
afterAll(async () => {
|
|
112
|
+
await dntShim.Deno.remove(dir, { recursive: true });
|
|
113
|
+
});
|
|
114
|
+
it("should return ture if directory is empty", async () => {
|
|
115
|
+
const result = await file.isEmptyDirectory(`${dir}/d`);
|
|
116
|
+
assertEquals(result, true);
|
|
117
|
+
});
|
|
118
|
+
it("should return false if directory is not empty", async () => {
|
|
119
|
+
const result = await file.isEmptyDirectory(`${dir}/b`);
|
|
120
|
+
assertEquals(result, false);
|
|
121
|
+
});
|
|
122
|
+
it("should return false if directory nor file does not exist", async () => {
|
|
123
|
+
const result = await file.isEmptyDirectory(`${dir}/x`);
|
|
124
|
+
assertEquals(result, false);
|
|
125
|
+
});
|
|
126
|
+
it("should return false if path is a file", async () => {
|
|
127
|
+
const result = await file.isEmptyDirectory(`${dir}/a.txt`);
|
|
128
|
+
assertEquals(result, false);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe("getAllDirFiles()", () => {
|
|
132
|
+
let dir;
|
|
133
|
+
const files = {
|
|
134
|
+
"a.txt": "hello",
|
|
135
|
+
"b": {
|
|
136
|
+
"c.txt": "world",
|
|
137
|
+
},
|
|
138
|
+
"d": {},
|
|
139
|
+
};
|
|
140
|
+
beforeAll(async () => {
|
|
141
|
+
dir = await dntShim.Deno.makeTempDir();
|
|
142
|
+
await prepareFiles(dir, files);
|
|
143
|
+
});
|
|
144
|
+
afterAll(async () => {
|
|
145
|
+
await dntShim.Deno.remove(dir, { recursive: true });
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
describe("untgz()", () => {
|
|
149
|
+
it("should extract tar.gz file", async () => {
|
|
150
|
+
const input = `
|
|
151
|
+
1f8b 0800 0000 0000 0003 edd5 df0a 8230
|
|
152
|
+
1c86 e11d 7715 5e81 6eee dff5 440a 4586
|
|
153
|
+
6046 75f7 b9a1 d091 d4c1 26d1 fb30 1c8c.
|
|
154
|
+
813f f8e0 5b59 89e4 e4c4 4b19 76e5 edbc
|
|
155
|
+
1b1f cf67 4219 e38c 9261 09a9 c23d 51d8
|
|
156
|
+
f4a3 0971 bb8e fba1 28c4 e5f4 dcaf dd6b
|
|
157
|
+
fac3 b91d 724c 9455 591d dbae ebcb f131
|
|
158
|
+
26fb 4708 d819 b392 bf5b f2d7 deaa 29ff
|
|
159
|
+
5a5a 270a 996c a237 7f9e 7f4c 7fb7 f514
|
|
160
|
+
d84a 59dd fba1 6b92 be02 b1ff adfd a4ff
|
|
161
|
+
b5f7 d3b9 52da d2ff 592c f9c7 6fa2 57e0
|
|
162
|
+
abfe 8ff9 ebda 28fa 3f87 983b fd0f 0000
|
|
163
|
+
0000 0000 0000 0000 3fef 0500 d28d ca00
|
|
164
|
+
2800 00
|
|
165
|
+
`
|
|
166
|
+
.replaceAll(" ", "")
|
|
167
|
+
.match(/.{2}/g)
|
|
168
|
+
?.map((byte) => parseInt(byte, 16));
|
|
169
|
+
const expected = [
|
|
170
|
+
{ name: "hello.txt", content: "hello\n" },
|
|
171
|
+
{ name: "world/world.txt", content: "world\n" },
|
|
172
|
+
];
|
|
173
|
+
const dir = await dntShim.Deno.makeTempDir();
|
|
174
|
+
await file.untgz(new Uint8Array(input), dir);
|
|
175
|
+
for (const { name, content } of expected) {
|
|
176
|
+
const actual = await dntShim.Deno.readTextFile(`${dir}/${name}`);
|
|
177
|
+
assertEquals(actual, content);
|
|
178
|
+
}
|
|
179
|
+
await dntShim.Deno.remove(dir, { recursive: true });
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
describe("toNixStyle()", () => {
|
|
183
|
+
describe("on Windows", () => {
|
|
184
|
+
const originalSeparator = file._internals.SEP;
|
|
185
|
+
beforeAll(() => {
|
|
186
|
+
file._internals.SEP = "/";
|
|
187
|
+
});
|
|
188
|
+
afterAll(() => {
|
|
189
|
+
file._internals.SEP = originalSeparator;
|
|
190
|
+
});
|
|
191
|
+
it("should return path with forward slashes", () => {
|
|
192
|
+
assertEquals(file.toNixStyle("a\\b\\c"), "a/b/c");
|
|
193
|
+
assertEquals(file.toNixStyle("a/b/c"), "a/b/c");
|
|
194
|
+
assertEquals(file.toNixStyle("a\\b/c"), "a/b/c");
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
describe("on Linux and macOS", () => {
|
|
198
|
+
const originalSeparator = file._internals.SEP;
|
|
199
|
+
beforeAll(() => {
|
|
200
|
+
file._internals.SEP = "/";
|
|
201
|
+
});
|
|
202
|
+
afterAll(() => {
|
|
203
|
+
file._internals.SEP = originalSeparator;
|
|
204
|
+
});
|
|
205
|
+
it("should return path with forward slashes", () => {
|
|
206
|
+
assertEquals(file.toNixStyle("a\\b\\c"), "a/b/c");
|
|
207
|
+
assertEquals(file.toNixStyle("a/b/c"), "a/b/c");
|
|
208
|
+
assertEquals(file.toNixStyle("a\\b/c"), "a/b/c");
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
describe("toNativeStyle()", () => {
|
|
213
|
+
describe("on Windows", () => {
|
|
214
|
+
const originalSeparator = file._internals.SEP;
|
|
215
|
+
beforeAll(() => {
|
|
216
|
+
file._internals.SEP = "\\";
|
|
217
|
+
});
|
|
218
|
+
afterAll(() => {
|
|
219
|
+
file._internals.SEP = originalSeparator;
|
|
220
|
+
});
|
|
221
|
+
it("should return path with forward slashes", () => {
|
|
222
|
+
assertEquals(file.toNativeStyle("a\\b\\c"), "a\\b\\c");
|
|
223
|
+
assertEquals(file.toNativeStyle("a/b/c"), "a\\b\\c");
|
|
224
|
+
assertEquals(file.toNativeStyle("a\\b/c"), "a\\b\\c");
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
describe("on Linux and macOS", () => {
|
|
228
|
+
const originalSeparator = file._internals.SEP;
|
|
229
|
+
beforeAll(() => {
|
|
230
|
+
file._internals.SEP = "/";
|
|
231
|
+
});
|
|
232
|
+
afterAll(() => {
|
|
233
|
+
file._internals.SEP = originalSeparator;
|
|
234
|
+
});
|
|
235
|
+
it("should return path with forward slashes", () => {
|
|
236
|
+
assertEquals(file.toNativeStyle("a\\b\\c"), "a/b/c");
|
|
237
|
+
assertEquals(file.toNativeStyle("a/b/c"), "a/b/c");
|
|
238
|
+
assertEquals(file.toNativeStyle("a\\b/c"), "a/b/c");
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
describe("getUniqueDirName()", () => {
|
|
243
|
+
let dir;
|
|
244
|
+
const files = {
|
|
245
|
+
"foo": {},
|
|
246
|
+
"foo_1": {},
|
|
247
|
+
"foo_2": {},
|
|
248
|
+
"bar": {},
|
|
249
|
+
};
|
|
250
|
+
beforeEach(async () => {
|
|
251
|
+
dir = await dntShim.Deno.makeTempDir();
|
|
252
|
+
await prepareFiles(dir, files);
|
|
253
|
+
});
|
|
254
|
+
afterEach(async () => {
|
|
255
|
+
await dntShim.Deno.remove(dir, { recursive: true });
|
|
256
|
+
});
|
|
257
|
+
it("should return unique name", async () => {
|
|
258
|
+
const actual = await file.getUniqueDirName("bar", { root: dir });
|
|
259
|
+
assertEquals(actual, "bar_1");
|
|
260
|
+
});
|
|
261
|
+
it("should return unique name with counter > 1 if the candidate exists", async () => {
|
|
262
|
+
const actual = await file.getUniqueDirName("foo", { root: dir });
|
|
263
|
+
assertEquals(actual, "foo_3");
|
|
264
|
+
});
|
|
265
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import * as dntShim from "../../_dnt.test_shims.js";
|
|
2
|
+
import * as mod from "../../src/shared/mod.js";
|
|
3
|
+
import { FileListType } from "../../src/shared/types.js";
|
|
4
|
+
import { assertEquals, assertStringIncludes } from "../../deps/deno.land/std@0.195.0/testing/asserts.js";
|
|
5
|
+
import { describe, it } from "../../deps/deno.land/std@0.195.0/testing/bdd.js";
|
|
6
|
+
import { assertSpyCall, restore, returnsNext, stub } from "../../deps/deno.land/std@0.195.0/testing/mock.js";
|
|
7
|
+
import * as colors from "../../deps/deno.land/std@0.195.0/fmt/colors.js";
|
|
8
|
+
describe("HttpStatus", () => {
|
|
9
|
+
// deno-fmt-ignore
|
|
10
|
+
const testcases = [
|
|
11
|
+
{ code: 200, isSuccess: true, isClientError: false, isServerError: false },
|
|
12
|
+
{ code: 201, isSuccess: true, isClientError: false, isServerError: false },
|
|
13
|
+
{ code: 400, isSuccess: false, isClientError: true, isServerError: false },
|
|
14
|
+
{ code: 403, isSuccess: false, isClientError: true, isServerError: false },
|
|
15
|
+
{ code: 500, isSuccess: false, isClientError: false, isServerError: true },
|
|
16
|
+
{ code: 503, isSuccess: false, isClientError: false, isServerError: true },
|
|
17
|
+
];
|
|
18
|
+
describe("HttpStatus.isSuccess(x: number)", () => {
|
|
19
|
+
testcases.forEach(({ code, isSuccess }) => {
|
|
20
|
+
it(`should return ${isSuccess} for ${code}`, () => {
|
|
21
|
+
assertEquals(mod.HttpStatus.isSuccess(code), isSuccess);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe("HttpStatus.isClientError(x: number)", () => {
|
|
26
|
+
testcases.forEach(({ code, isClientError }) => {
|
|
27
|
+
it(`should return ${isClientError} for ${code}`, () => {
|
|
28
|
+
assertEquals(mod.HttpStatus.isClientError(code), isClientError);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe("HttpStatus.isServerError(x: number)", () => {
|
|
33
|
+
testcases.forEach(({ code, isServerError }) => {
|
|
34
|
+
it(`should return ${isServerError} for ${code}`, () => {
|
|
35
|
+
assertEquals(mod.HttpStatus.isServerError(code), isServerError);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
it("HttpStatus for Response should work", () => {
|
|
40
|
+
testcases.forEach((testcase) => {
|
|
41
|
+
const response = new dntShim.Response("hello", { status: testcase.code });
|
|
42
|
+
assertEquals(mod.HttpStatus.isSuccess(response), testcase.isSuccess);
|
|
43
|
+
assertEquals(mod.HttpStatus.isClientError(response), testcase.isClientError);
|
|
44
|
+
assertEquals(mod.HttpStatus.isServerError(response), testcase.isServerError);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe("Prompt.select()", () => {
|
|
49
|
+
describe(".select()", () => {
|
|
50
|
+
const items = ["a", "b", "c"];
|
|
51
|
+
// deno-fmt-ignore-start
|
|
52
|
+
const testcases = [
|
|
53
|
+
{ input: "1", expected: 0, message: "should return selected item index (0 start) - 1" },
|
|
54
|
+
{ input: "2", expected: 1, message: "should return selected item index (0 start) - 2" },
|
|
55
|
+
{ input: "3", expected: 2, message: "should return selected item index (0 start) - 3" },
|
|
56
|
+
{ input: "4", expected: undefined, message: "should return undefined if input is out of range" },
|
|
57
|
+
{ input: null, expected: undefined, message: "should return undefined for empty input" },
|
|
58
|
+
{ input: "x", expected: undefined, message: "should return undefined for invalid input" },
|
|
59
|
+
];
|
|
60
|
+
testcases.forEach(({ input, expected, message }) => {
|
|
61
|
+
it(message, () => {
|
|
62
|
+
const promptSpy = stub(mod._internals, "prompt", returnsNext([input]));
|
|
63
|
+
const actual = mod.Prompt.select("Select one", items);
|
|
64
|
+
assertEquals(actual, expected);
|
|
65
|
+
assertSpyCall(promptSpy, 0, {
|
|
66
|
+
args: ["Select one\n[1] a\n[2] b\n[3] c\n> "],
|
|
67
|
+
});
|
|
68
|
+
restore();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("printTimeLeft()", () => {
|
|
74
|
+
const noColor = (x) => x;
|
|
75
|
+
// deno-fmt-ignore
|
|
76
|
+
const testcases = [
|
|
77
|
+
{ input: 0, expected: "0s", color: colors.red },
|
|
78
|
+
{ input: 1, expected: "1s", color: colors.red },
|
|
79
|
+
{ input: 59, expected: "59s", color: colors.red },
|
|
80
|
+
{ input: 60, expected: "1m", color: colors.red },
|
|
81
|
+
{ input: 300, expected: "5m", color: colors.red },
|
|
82
|
+
{ input: 301, expected: "5m 1s", color: colors.yellow },
|
|
83
|
+
{ input: 3599, expected: "59m 59s", color: colors.yellow },
|
|
84
|
+
{ input: 3600, expected: "1h", color: colors.yellow },
|
|
85
|
+
{ input: -1, expected: "The exam deadline has not yet been started", color: noColor },
|
|
86
|
+
{ input: -2, expected: "The exam deadline has passed", color: noColor },
|
|
87
|
+
];
|
|
88
|
+
testcases.forEach(({ input, expected, color }) => {
|
|
89
|
+
it(`should print collect time for ${input} second(s)`, () => {
|
|
90
|
+
const consoleSpy = stub(console, "log");
|
|
91
|
+
mod.printTimeLeft(input);
|
|
92
|
+
const actual = consoleSpy.calls.flatMap((call) => call.args).join("\n");
|
|
93
|
+
assertStringIncludes(actual, color(expected));
|
|
94
|
+
restore();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe("listFileNames()", () => {
|
|
99
|
+
const context = {
|
|
100
|
+
settings: {
|
|
101
|
+
files: {
|
|
102
|
+
"test": {
|
|
103
|
+
"settings.json": false,
|
|
104
|
+
"basic.js": false,
|
|
105
|
+
"basic_testcases.json": false,
|
|
106
|
+
"in": {
|
|
107
|
+
"basic": {
|
|
108
|
+
"1.in": false,
|
|
109
|
+
"2.in": false,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
"out": {
|
|
113
|
+
"basic": {
|
|
114
|
+
"1.out": false,
|
|
115
|
+
"2.out": false,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
"readme.md": true,
|
|
120
|
+
"Cargo.toml": true,
|
|
121
|
+
"src": {
|
|
122
|
+
"main.rs": true,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
hiddenFiles: [
|
|
126
|
+
"test/in/secret/1.in",
|
|
127
|
+
"test/in/secret/2.in",
|
|
128
|
+
"test/out/secret/1.out",
|
|
129
|
+
"test/out/secret/2.out",
|
|
130
|
+
"test/secret_testcases.json",
|
|
131
|
+
"test/secret.js",
|
|
132
|
+
],
|
|
133
|
+
testExcludes: [],
|
|
134
|
+
images: [],
|
|
135
|
+
build: ["cargo build --release"],
|
|
136
|
+
test: "cargo test",
|
|
137
|
+
mainFile: "src/main.rs",
|
|
138
|
+
allowNewFile: true,
|
|
139
|
+
languages: ["rust"],
|
|
140
|
+
initialize: [],
|
|
141
|
+
},
|
|
142
|
+
answers: {
|
|
143
|
+
build: ["cargo build --release"],
|
|
144
|
+
appCommand: "./target/release/main",
|
|
145
|
+
mainFile: "src/main.rs",
|
|
146
|
+
addedFiles: ["src/lib.rs"],
|
|
147
|
+
fileVersions: {},
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
const testcases = [
|
|
151
|
+
{
|
|
152
|
+
target: "public files",
|
|
153
|
+
type: FileListType.All,
|
|
154
|
+
expected: [
|
|
155
|
+
"Cargo.toml",
|
|
156
|
+
"readme.md",
|
|
157
|
+
"src/lib.rs",
|
|
158
|
+
"src/main.rs",
|
|
159
|
+
"test/basic.js",
|
|
160
|
+
"test/basic_testcases.json",
|
|
161
|
+
"test/in/basic/1.in",
|
|
162
|
+
"test/in/basic/2.in",
|
|
163
|
+
"test/out/basic/1.out",
|
|
164
|
+
"test/out/basic/2.out",
|
|
165
|
+
"test/settings.json",
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
target: "readonly public files",
|
|
170
|
+
type: FileListType.ReadOnlyChallengeFiles,
|
|
171
|
+
expected: [
|
|
172
|
+
"test/basic.js",
|
|
173
|
+
"test/basic_testcases.json",
|
|
174
|
+
"test/in/basic/1.in",
|
|
175
|
+
"test/in/basic/2.in",
|
|
176
|
+
"test/out/basic/1.out",
|
|
177
|
+
"test/out/basic/2.out",
|
|
178
|
+
"test/settings.json",
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
target: "editable files",
|
|
183
|
+
type: FileListType.EditableChallengeFiles,
|
|
184
|
+
expected: [
|
|
185
|
+
"Cargo.toml",
|
|
186
|
+
"readme.md",
|
|
187
|
+
"src/main.rs",
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
target: "public files",
|
|
192
|
+
type: FileListType.UserAddedFiles,
|
|
193
|
+
expected: [
|
|
194
|
+
"src/lib.rs",
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
target: "public files",
|
|
199
|
+
type: FileListType.Editable,
|
|
200
|
+
expected: [
|
|
201
|
+
"Cargo.toml",
|
|
202
|
+
"readme.md",
|
|
203
|
+
"src/lib.rs",
|
|
204
|
+
"src/main.rs",
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
testcases.forEach(({ target, type, expected }) => {
|
|
209
|
+
const flt = (Object.entries(FileListType).find(([_, v]) => v === type) ?? ["", 0])[0];
|
|
210
|
+
it(`should list all ${target} with FileListType.${flt}`, () => {
|
|
211
|
+
const actual = mod.listFileNames(context, type);
|
|
212
|
+
actual.sort();
|
|
213
|
+
assertEquals(actual, expected);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
describe("SnakeCase", () => {
|
|
218
|
+
describe(".to()", () => {
|
|
219
|
+
it("should convert string correctly", () => {
|
|
220
|
+
assertEquals(mod.SnakeCase.to("helloWorld"), "hello_world");
|
|
221
|
+
});
|
|
222
|
+
it("should keep string as is if it is already snake case", () => {
|
|
223
|
+
assertEquals(mod.SnakeCase.to("hello_world"), "hello_world");
|
|
224
|
+
});
|
|
225
|
+
it("should accept empty object", () => {
|
|
226
|
+
assertEquals(mod.SnakeCase.to({}), {});
|
|
227
|
+
});
|
|
228
|
+
it("should convert object correctly", () => {
|
|
229
|
+
assertEquals(mod.SnakeCase.to({
|
|
230
|
+
helloWorld: "foo",
|
|
231
|
+
}), {
|
|
232
|
+
hello_world: "foo",
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
it("should keep object as is if it is already snake case", () => {
|
|
236
|
+
// @ts-ignore key of the object may change
|
|
237
|
+
assertEquals(mod.SnakeCase.to({
|
|
238
|
+
hello_world: "foo",
|
|
239
|
+
}), {
|
|
240
|
+
hello_world: "foo",
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
it("should keep object values as is", () => {
|
|
244
|
+
// @ts-ignore key of the object may change
|
|
245
|
+
assertEquals(mod.SnakeCase.to({
|
|
246
|
+
helloWorld: "helloWorld",
|
|
247
|
+
another_world: "anotherWorld",
|
|
248
|
+
}), {
|
|
249
|
+
hello_world: "helloWorld",
|
|
250
|
+
another_world: "anotherWorld",
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
it("should keep array as is", () => {
|
|
254
|
+
assertEquals(mod.SnakeCase.to(["helloWorld", "another_world"]), ["helloWorld", "another_world"]);
|
|
255
|
+
assertEquals(mod.SnakeCase.to({
|
|
256
|
+
helloWorld: ["helloWorld", "another_world"],
|
|
257
|
+
}), {
|
|
258
|
+
hello_world: ["helloWorld", "another_world"],
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
it("should treat nested object correctly", () => {
|
|
262
|
+
assertEquals(mod.SnakeCase.to({
|
|
263
|
+
helloWorld: {
|
|
264
|
+
anotherWorld: "fooBar",
|
|
265
|
+
},
|
|
266
|
+
}), {
|
|
267
|
+
hello_world: {
|
|
268
|
+
another_world: "fooBar",
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
it("should ignore primitive types other than strings", () => {
|
|
273
|
+
assertEquals(mod.SnakeCase.to(1), 1);
|
|
274
|
+
assertEquals(mod.SnakeCase.to(true), true);
|
|
275
|
+
assertEquals(mod.SnakeCase.to(undefined), undefined);
|
|
276
|
+
assertEquals(mod.SnakeCase.to(null), null);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
describe(".from()", () => {
|
|
280
|
+
it("should convert string correctly", () => {
|
|
281
|
+
assertEquals(mod.SnakeCase.from("hello_world"), "helloWorld");
|
|
282
|
+
});
|
|
283
|
+
it("should keep string as is if it is already camelCase", () => {
|
|
284
|
+
assertEquals(mod.SnakeCase.from("helloWorld"), "helloWorld");
|
|
285
|
+
});
|
|
286
|
+
it("should accept empty object", () => {
|
|
287
|
+
assertEquals(mod.SnakeCase.to({}), {});
|
|
288
|
+
});
|
|
289
|
+
it("should convert object correctly", () => {
|
|
290
|
+
assertEquals(mod.SnakeCase.from({
|
|
291
|
+
hello_world: "foo",
|
|
292
|
+
}), {
|
|
293
|
+
helloWorld: "foo",
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
it("should keep object as is if it is already camelCase", () => {
|
|
297
|
+
// @ts-ignore key of the object may change
|
|
298
|
+
assertEquals(mod.SnakeCase.from({
|
|
299
|
+
helloWorld: "foo",
|
|
300
|
+
}), {
|
|
301
|
+
helloWorld: "foo",
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
it("should keep object values as is", () => {
|
|
305
|
+
// @ts-ignore key of the object may change
|
|
306
|
+
assertEquals(mod.SnakeCase.from({
|
|
307
|
+
helloWorld: "hello_world",
|
|
308
|
+
another_world: "another_world",
|
|
309
|
+
}), {
|
|
310
|
+
helloWorld: "hello_world",
|
|
311
|
+
anotherWorld: "another_world",
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
it("should keep array as is", () => {
|
|
315
|
+
assertEquals(mod.SnakeCase.from(["helloWorld", "another_world"]), ["helloWorld", "another_world"]);
|
|
316
|
+
assertEquals(mod.SnakeCase.from({
|
|
317
|
+
hello_world: ["helloWorld", "another_world"],
|
|
318
|
+
}), {
|
|
319
|
+
helloWorld: ["helloWorld", "another_world"],
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
it("should treat nested object correctly", () => {
|
|
323
|
+
assertEquals(mod.SnakeCase.from({
|
|
324
|
+
hello_world: {
|
|
325
|
+
another_world: "foo_bar",
|
|
326
|
+
},
|
|
327
|
+
}), {
|
|
328
|
+
helloWorld: {
|
|
329
|
+
anotherWorld: "foo_bar",
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
it("should ignore primitive types other than strings", () => {
|
|
334
|
+
assertEquals(mod.SnakeCase.from(1), 1);
|
|
335
|
+
assertEquals(mod.SnakeCase.from(true), true);
|
|
336
|
+
assertEquals(mod.SnakeCase.from(undefined), undefined);
|
|
337
|
+
assertEquals(mod.SnakeCase.from(null), null);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
describe("scoreRatio() (Inner method)", () => {
|
|
342
|
+
it("should return `N/A` if `track run` is never executed", () => {
|
|
343
|
+
assertStringIncludes(mod._internals.scoreRatio(undefined, 10), "N/A");
|
|
344
|
+
});
|
|
345
|
+
it("should return `N/A` for challenges without open testcase", () => {
|
|
346
|
+
assertStringIncludes(mod._internals.scoreRatio(undefined, undefined), "N/A");
|
|
347
|
+
});
|
|
348
|
+
it("should return score if `track run` is once after executed", () => {
|
|
349
|
+
assertStringIncludes(mod._internals.scoreRatio(0, 10), "0/10");
|
|
350
|
+
assertStringIncludes(mod._internals.scoreRatio(5, 10), "5/10");
|
|
351
|
+
assertStringIncludes(mod._internals.scoreRatio(10, 10), "10/10");
|
|
352
|
+
});
|
|
353
|
+
});
|