Skip to content

Commit 98dd7ac

Browse files
committed
more tests
1 parent d256ec6 commit 98dd7ac

File tree

3 files changed

+256
-31
lines changed

3 files changed

+256
-31
lines changed

src/parsers/TimeParser.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { P } from '@lemons_dev/parsinom/lib/ParsiNOM';
2+
import { P_UTILS } from '@lemons_dev/parsinom/lib/ParserUtils';
3+
14
export class Time {
25
private _hour: number;
36
private _minute: number;
@@ -50,6 +53,20 @@ export class Time {
5053
}
5154
}
5255

56+
const timeParser = P.sequenceMap(
57+
(a, b, _, c, d) => {
58+
return {
59+
hour: Number.parseInt(a + b),
60+
minute: Number.parseInt(c + d),
61+
};
62+
},
63+
P_UTILS.digit(),
64+
P_UTILS.digit(),
65+
P.string(':'),
66+
P_UTILS.digit(),
67+
P_UTILS.digit(),
68+
);
69+
5370
export class TimeParser {
5471
public static parse(timeString: string | null | undefined): Time | undefined {
5572
if (!timeString) {
@@ -58,13 +75,23 @@ export class TimeParser {
5875

5976
const time: Time = TimeParser.getDefaultTime();
6077

61-
const timeParts = timeString.split(':');
62-
if (timeParts.length !== 2) {
78+
const parsedTime = timeParser.tryParse(timeString);
79+
80+
if (!parsedTime.success) {
81+
return undefined;
82+
}
83+
if (Number.isNaN(parsedTime.value.hour) || Number.isNaN(parsedTime.value.minute)) {
84+
return undefined;
85+
}
86+
if (parsedTime.value.hour < 0 || parsedTime.value.hour >= 24) {
87+
return undefined;
88+
}
89+
if (parsedTime.value.minute < 0 || parsedTime.value.minute >= 60) {
6390
return undefined;
6491
}
6592

66-
time.setHourFromString(timeParts[0]);
67-
time.setMinuteFromString(timeParts[1]);
93+
time.setHour(parsedTime.value.hour);
94+
time.setMinute(parsedTime.value.minute);
6895

6996
return time;
7097
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { describe, test, expect } from 'bun:test';
2+
import { BindTargetDeclaration } from '../../src/parsers/inputFieldParser/InputFieldDeclaration';
3+
import { TestPlugin } from './mocks/TestAPI';
4+
import { BindTargetScope } from '../../src/metadata/BindTargetScope';
5+
6+
const plugin = new TestPlugin();
7+
const parser = plugin.api.bindTargetParser;
8+
9+
const validBindTargetFiles = [
10+
undefined,
11+
'file',
12+
'testFile',
13+
'test_file',
14+
'test-file',
15+
'test file',
16+
'path/to/file',
17+
'something/in/japanese こんにちは',
18+
'this is/some-path/to_some_file/in chinese/你叫什么名字',
19+
];
20+
21+
const validBindTargetPaths: [string, string[]][] = [
22+
['test', ['test']],
23+
['["test"]', ['test']],
24+
['this["is"]', ['this', 'is']],
25+
['this["is"]["a"]', ['this', 'is', 'a']],
26+
['this["is"]["a"].path', ['this', 'is', 'a', 'path']],
27+
['["this"].is.a["path"]', ['this', 'is', 'a', 'path']],
28+
['[0]', ['0']],
29+
['test[0]', ['test', '0']],
30+
['test["foo"][1]', ['test', 'foo', '1']],
31+
['test["foo"][1].bar', ['test', 'foo', '1', 'bar']],
32+
];
33+
34+
const localScopeBindTarget: BindTargetDeclaration = {
35+
filePath: 'scope_test_file',
36+
metadataPath: ['scope_test_metadata'],
37+
boundToLocalScope: false,
38+
listenToChildren: false,
39+
};
40+
41+
const localScope = new BindTargetScope(localScopeBindTarget);
42+
43+
interface BindTargetCombination {
44+
filePath: string | undefined;
45+
metadataPath: string[];
46+
metadataPathString: string;
47+
}
48+
49+
function generateValidBindTargets(): BindTargetCombination[] {
50+
const testCases: BindTargetCombination[] = [];
51+
for (const validBindTargetFile of validBindTargetFiles) {
52+
for (const validBindTargetPath of validBindTargetPaths) {
53+
testCases.push({
54+
filePath: validBindTargetFile,
55+
metadataPathString: validBindTargetPath[0],
56+
metadataPath: validBindTargetPath[1],
57+
});
58+
}
59+
}
60+
return testCases;
61+
}
62+
63+
interface TestCasePart {
64+
str: string;
65+
expected: BindTargetDeclaration;
66+
}
67+
68+
interface TestCase {
69+
normal: TestCasePart;
70+
local: TestCasePart;
71+
}
72+
73+
function generateTestCase(combination: BindTargetCombination): TestCase {
74+
if (combination.filePath === undefined) {
75+
let localStr: string;
76+
if (combination.metadataPathString.startsWith('[')) {
77+
localStr = `^${combination.metadataPathString}`;
78+
} else {
79+
localStr = `^.${combination.metadataPathString}`;
80+
}
81+
82+
return {
83+
normal: {
84+
str: combination.metadataPathString,
85+
expected: {
86+
filePath: undefined,
87+
metadataPath: combination.metadataPath,
88+
listenToChildren: false,
89+
boundToLocalScope: false,
90+
},
91+
},
92+
local: {
93+
str: localStr,
94+
expected: {
95+
filePath: localScopeBindTarget.filePath,
96+
metadataPath: localScopeBindTarget.metadataPath.concat(combination.metadataPath),
97+
listenToChildren: false,
98+
boundToLocalScope: true,
99+
},
100+
},
101+
};
102+
} else {
103+
let localStr: string;
104+
if (combination.metadataPathString.startsWith('[')) {
105+
localStr = `${combination.filePath}#^${combination.metadataPathString}`;
106+
} else {
107+
localStr = `${combination.filePath}#^.${combination.metadataPathString}`;
108+
}
109+
110+
return {
111+
normal: {
112+
str: `${combination.filePath}#${combination.metadataPathString}`,
113+
expected: {
114+
filePath: combination.filePath,
115+
metadataPath: combination.metadataPath,
116+
listenToChildren: false,
117+
boundToLocalScope: false,
118+
},
119+
},
120+
local: {
121+
str: localStr,
122+
expected: {
123+
filePath: localScopeBindTarget.filePath,
124+
metadataPath: localScopeBindTarget.metadataPath.concat(combination.metadataPath),
125+
listenToChildren: false,
126+
boundToLocalScope: true,
127+
},
128+
},
129+
};
130+
}
131+
}
132+
133+
describe('bind target parser', () => {
134+
describe('should succeed on valid bind target', () => {
135+
for (const bindTarget of generateValidBindTargets()) {
136+
const testCase = generateTestCase(bindTarget);
137+
138+
test(testCase.normal.str, () => {
139+
expect(parser.parseAndValidateBindTarget(testCase.normal.str)).toEqual(testCase.normal.expected);
140+
});
141+
142+
test(testCase.local.str, () => {
143+
expect(parser.parseAndValidateBindTarget(testCase.local.str, localScope)).toEqual(testCase.local.expected);
144+
});
145+
}
146+
});
147+
});
Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,87 @@
11
import { Time, TimeParser } from '../../src/parsers/TimeParser';
2-
import { describe, test, expect } from 'bun:test';
2+
import { describe, test, expect, beforeEach } from 'bun:test';
33

4-
test('stringify time', () => {
5-
const time = new Time();
6-
time.setHour(6);
7-
time.setMinute(9);
4+
describe('time stringify', () => {
5+
describe('should stringify time', () => {
6+
test.each([
7+
['00:00', 0, 0],
8+
['01:05', 1, 5],
9+
['03:25', 3, 25],
10+
['23:59', 23, 59],
11+
])('%s', (expected, hour, minute) => {
12+
const time = new Time();
13+
time.setHour(hour);
14+
time.setMinute(minute);
815

9-
expect(TimeParser.stringify(time)).toEqual('06:09');
16+
expect(TimeParser.stringify(time)).toEqual(expected);
17+
});
18+
});
1019
});
1120

12-
test('parse time', () => {
13-
const time = new Time();
14-
time.setHour(6);
15-
time.setMinute(9);
21+
describe('time parser', () => {
22+
describe('should succeed on valid time', () => {
23+
test.each([
24+
['00:00', 0, 0],
25+
['01:05', 1, 5],
26+
['03:25', 3, 25],
27+
['23:59', 23, 59],
28+
])('%s', (time, expectedHour, expectedMinute) => {
29+
expect(TimeParser.parse(time)?.getHour()).toBe(expectedHour);
30+
expect(TimeParser.parse(time)?.getMinute()).toBe(expectedMinute);
31+
});
32+
});
1633

17-
expect(TimeParser.parse('06:09')).toEqual(time);
34+
describe('should fail on invalid time format', () => {
35+
test.each([['00:0'], ['0:05'], ['304:25'], ['25a'], [''], ['23h:15m'], ['23#15']])('%s', time => {
36+
// @ts-ignore
37+
expect(TimeParser.parse(time)).toBe(undefined);
38+
});
39+
});
40+
41+
describe('should fail on time out of bounds', () => {
42+
test.each([['-01:00'], ['24:05'], ['26:05'], ['05:60'], ['05:99']])('%s', time => {
43+
// @ts-ignore
44+
expect(TimeParser.parse(time)).toBe(undefined);
45+
});
46+
});
1847
});
1948

20-
test('Time edge cases', () => {
21-
const time = new Time();
22-
23-
time.setMinute(100);
24-
expect(time.getMinute()).not.toEqual(100);
25-
time.setMinute(-1);
26-
expect(time.getMinute()).not.toEqual(-1);
27-
time.setMinuteFromString('a');
28-
expect(time.getMinute()).not.toEqual('a');
29-
30-
time.setHour(50);
31-
expect(time.getHour()).not.toEqual(50);
32-
time.setHour(-1);
33-
expect(time.getHour()).not.toEqual(-1);
34-
time.setHourFromString('a');
35-
expect(time.getHour()).not.toEqual('a');
49+
describe('time class', () => {
50+
describe('should not be possible to crete invalid time', () => {
51+
let time: Time;
52+
53+
beforeEach(() => {
54+
time = new Time();
55+
});
56+
57+
test('set minute to 100', () => {
58+
time.setMinute(100);
59+
expect(time.getMinute()).not.toEqual(100);
60+
});
61+
62+
test('set minute to -1', () => {
63+
time.setMinute(-1);
64+
expect(time.getMinute()).not.toEqual(-1);
65+
});
66+
67+
test("set minute to 'a'", () => {
68+
time.setMinuteFromString('a');
69+
expect(time.getMinute()).not.toEqual('a');
70+
});
71+
72+
test('set hour to 50', () => {
73+
time.setHour(50);
74+
expect(time.getHour()).not.toEqual(50);
75+
});
76+
77+
test('set hour to -1', () => {
78+
time.setHour(-1);
79+
expect(time.getHour()).not.toEqual(-1);
80+
});
81+
82+
test("set hour to 'a'", () => {
83+
time.setHourFromString('a');
84+
expect(time.getHour()).not.toEqual('a');
85+
});
86+
});
3687
});

0 commit comments

Comments
 (0)