Skip to content

Commit 320b4ba

Browse files
committed
Merge branch 'main' of https://github.com/stackpress/idea
2 parents d269e79 + 51fa585 commit 320b4ba

File tree

9 files changed

+414
-15
lines changed

9 files changed

+414
-15
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { describe, it } from 'mocha';
2+
import { expect, use } from 'chai';
3+
import Compiler from '../../idea-parser/src/Compiler'
4+
import {
5+
DataToken,
6+
DeclarationToken,
7+
IdentifierToken,
8+
ImportToken,
9+
SchemaToken
10+
} from '../src/types';
11+
12+
describe('Compiler Test', () => {
13+
14+
// Line 46
15+
it('Should throw an exception with the message "Invalid data token type" when an invalid token type is encountered', () => {
16+
const invalidToken = { type: 'InvalidType' } as unknown as DataToken;
17+
expect(() => Compiler.data(invalidToken)).to.throw('Invalid data token type');
18+
});
19+
20+
// Line 54
21+
it('Should throw an exception with the message "Invalid Enum" when an invalid enum is encountered', () => {
22+
const invalidEnumToken = { kind: 'notAnEnum', declarations: [] } as unknown as DeclarationToken;
23+
expect(() => Compiler.enum(invalidEnumToken)).to.throw('Invalid Enum');
24+
});
25+
26+
// Line 86
27+
it('Should throw an exception with the message "Unknown reference {token.name}" when references is an empty object', () => {
28+
const token = { name: 'someReference' } as IdentifierToken;
29+
expect(() => Compiler.identifier(token, {})).to.throw('Unknown reference someReference');
30+
});
31+
32+
33+
// Line 109
34+
it('Should throw an exception with the message "Expecting a columns property" when the columns property is missing', () => {
35+
const tokenWithoutColumns = { kind: 'model',
36+
declarations: [{
37+
id: { name: 'TestModel' },
38+
init: { properties: [] }
39+
}]
40+
} as unknown as DeclarationToken;
41+
expect(() => Compiler.model(tokenWithoutColumns)).to.throw('Expecting a columns property');
42+
});
43+
44+
// Line 152
45+
it('Should throw an exception with the message "Invalid Plugin" when an invalid plugin is encountered', () => {
46+
const invalidPluginToken = { kind: 'notAPlugin', declarations: [] } as unknown as DeclarationToken;
47+
expect(() => Compiler.plugin(invalidPluginToken)).to.throw('Invalid Plugin');
48+
});
49+
50+
// Line 168
51+
it('Should throw an exception with the message "Invalid Prop" when an invalid property is encountered', () => {
52+
const invalidPropToken = { kind: 'notAProp', declarations: [] } as unknown as DeclarationToken;
53+
expect(() => Compiler.prop(invalidPropToken)).to.throw('Invalid Prop');
54+
});
55+
56+
// Line 184
57+
it('Should throw an exception with the message "Invalid Schema" when an invalid schema is encountered', () => {
58+
const invalidSchemaToken = { kind: 'notASchema', body: [] } as unknown as SchemaToken;
59+
expect(() => Compiler.schema(invalidSchemaToken)).to.throw('Invalid Schema');
60+
});
61+
62+
// Line 205
63+
it('Should throw an exception with the message "Duplicate key" when a duplicate key is encountered in the schema', () => {
64+
const duplicateKeyToken = {
65+
kind: 'schema',
66+
body: [
67+
{
68+
kind: 'enum',
69+
declarations: [{
70+
id: { name: 'DuplicateKey' },
71+
init: { properties: [{ key: { name: 'key1' }, value: { value: 'value1' } }] }
72+
}]
73+
},
74+
{
75+
kind: 'enum',
76+
declarations: [{
77+
id: { name: 'DuplicateKey' },
78+
init: { properties: [{ key: { name: 'key2' }, value: { value: 'value2' } }] }
79+
}]
80+
}
81+
]
82+
} as unknown as SchemaToken;
83+
expect(() => Compiler.schema(duplicateKeyToken)).to.throw('Duplicate DuplicateKey');
84+
});
85+
86+
// Line 260
87+
it('Should throw an exception with the message "Invalid Type" when an invalid type is encountered', () => {
88+
const invalidTypeToken = { kind: 'notAType', declarations: [] } as unknown as DeclarationToken;
89+
expect(() => Compiler.type(invalidTypeToken)).to.throw('Invalid Type');
90+
});
91+
92+
// Line 304
93+
it('Should throw an exception with the message "Invalid Import" when an invalid import is encountered', () => {
94+
const invalidImportToken = { type: 'NotAnImportDeclaration', source: { value: './invalid.idea' } } as unknown as ImportToken;
95+
expect(() => Compiler.use(invalidImportToken)).to.throw('Invalid Import');
96+
});
97+
98+
99+
100+
101+
102+
})

packages/idea-parser/tests/EnumTree.test.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,47 @@ import EnumTree from '../src/trees/EnumTree';
66

77
use(deepEqualInAnyOrder);
88

9+
/*
10+
* The cleanAST function is used to remove start and end
11+
* properties from ASTs for comparison.
12+
*/
13+
const cleanAST = (node: any) => {
14+
if (typeof node === 'object' && node !== null) {
15+
const { start, end, ...rest } = node;
16+
return Object.keys(rest).reduce((acc, key) => {
17+
acc[key] = Array.isArray(rest[key])
18+
? rest[key].map(cleanAST)
19+
: cleanAST(rest[key]);
20+
return acc;
21+
}, {});
22+
}
23+
return node;
24+
};
25+
926
describe('Enum Tree', () => {
1027
it('Should parse Enums', async () => {
11-
const actual = EnumTree.parse(fs.readFileSync(`${__dirname}/fixtures/enum.idea`, 'utf8'));
12-
const expected = JSON.parse(fs.readFileSync(`${__dirname}/fixtures/enum.json`, 'utf8'));
28+
const actualRaw = EnumTree.parse(fs.readFileSync(`${__dirname}/fixtures/enum.idea`, 'utf8'));
29+
const expectedRaw = JSON.parse(fs.readFileSync(`${__dirname}/fixtures/enum.json`, 'utf8'));
30+
31+
const actual = cleanAST(actualRaw);
32+
const expected = cleanAST(expectedRaw);
1333
//console.log(JSON.stringify(actual, null, 2));
1434
expect(actual).to.deep.equalInAnyOrder(expected);
1535
});
36+
37+
// Line 37
38+
it('Should throw an error when the input is an empty string', () => {
39+
expect(() => {
40+
const lexerMock = {
41+
expect: (tokenType: string) => { throw new Error('Unexpected end of input'); },
42+
load: () => {}
43+
};
44+
const enumTree = new EnumTree();
45+
(enumTree as any)._lexer = lexerMock;
46+
enumTree.parse('');
47+
}).to.throw(Error, 'Unexpected end of input');
48+
});
49+
50+
51+
1652
});

packages/idea-parser/tests/Lexer.test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import type {
22
LiteralToken,
33
ObjectToken,
44
ArrayToken,
5-
UnknownToken
5+
UnknownToken,
6+
Token
67
} from '../src/types';
78

89
import { describe, it } from 'mocha';
@@ -254,4 +255,51 @@ describe('Lexer/Compiler', () => {
254255
expect(token.end).to.equal(45);
255256
})();
256257
});
258+
259+
// Line 18
260+
it('Should handle an empty input string and return an appropriate token', () => {
261+
const lexer = new Lexer();
262+
lexer.load('');
263+
const token = lexer.read();
264+
expect(token).to.be.undefined;
265+
});
266+
267+
// Line 58
268+
it('Should throw an exception for an unknown definition key', () => {
269+
const lexer = new Lexer();
270+
expect(() => lexer.expect('unknownKey')).to.throw('Unknown definition unknownKey');
271+
});
272+
273+
// Line 117
274+
it('Should handle the case when keys parameter is explicitly passed as undefined', () => {
275+
const lexer = new Lexer();
276+
lexer.define('literal', (code, start) => {
277+
if (code.startsWith('42', start)) {
278+
return { type: 'Literal', value: 42, start, end: start + 2 } as Token;
279+
}
280+
return undefined;
281+
});
282+
lexer.load('42');
283+
const token = lexer.match('42', 0, undefined);
284+
expect(token).to.deep.equal({ type: 'Literal', value: 42, start: 0, end: 2 });
285+
});
286+
287+
// Line 121
288+
it('Should throw an exception when a key is missing from the dictionary', () => {
289+
const lexer = new Lexer();
290+
lexer.load('some code');
291+
expect(() => lexer.match('some code', 0, ['missingKey'])).to.throw('Unknown definition missingKey');
292+
});
293+
294+
// Line 174
295+
it('Should return an empty string when start and end are the same', () => {
296+
const lexer = new Lexer();
297+
lexer.load('some code');
298+
const result = lexer.substring(5, 5);
299+
expect(result).to.equal('');
300+
});
301+
302+
303+
304+
257305
});

packages/idea-parser/tests/ModelTree.test.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,52 @@ import ModelTree from '../src/trees/ModelTree';
66

77
use(deepEqualInAnyOrder);
88

9+
/*
10+
* The cleanAST function is used to remove start and end
11+
* properties from ASTs for comparison.
12+
*/
13+
const cleanAST = (node: any) => {
14+
if (typeof node === 'object' && node !== null) {
15+
const { start, end, ...rest } = node;
16+
return Object.keys(rest).reduce((acc, key) => {
17+
acc[key] = Array.isArray(rest[key])
18+
? rest[key].map(cleanAST)
19+
: cleanAST(rest[key]);
20+
return acc;
21+
}, {});
22+
}
23+
return node;
24+
};
25+
926
describe('Model Tree', () => {
1027
it('Should parse Model', async () => {
11-
const actual = ModelTree.parse(fs.readFileSync(`${__dirname}/fixtures/model.idea`, 'utf8'));
12-
const expected = JSON.parse(fs.readFileSync(`${__dirname}/fixtures/model.json`, 'utf8'));
28+
const actualRaw = ModelTree.parse(fs.readFileSync(`${__dirname}/fixtures/model.idea`, 'utf8'));
29+
const expectedRaw = JSON.parse(fs.readFileSync(`${__dirname}/fixtures/model.json`, 'utf8'));
30+
31+
const actual = cleanAST(actualRaw);
32+
const expected = cleanAST(expectedRaw);
1333
//console.log(JSON.stringify(actual, null, 2));
1434
expect(actual).to.deep.equalInAnyOrder(expected);
1535
});
36+
37+
// Line 37
38+
it('Should throw an error if the lexer does not return an IdentifierToken when expecting CapitalIdentifier', () => {
39+
const lexerMock = {
40+
expect: (tokenType: string) => {
41+
if (tokenType === 'CapitalIdentifier') {
42+
throw new Error('Expected CapitalIdentifier but got something else');
43+
}
44+
},
45+
load: () => {}
46+
};
47+
48+
const modelTree = new ModelTree();
49+
modelTree['_lexer'] = lexerMock as any;
50+
51+
expect(() => modelTree.parse('model foobar')).to.throw('Expected CapitalIdentifier but got something else');
52+
});
53+
54+
55+
56+
1657
});

packages/idea-parser/tests/PluginTree.test.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,45 @@ import PluginTree from '../src/trees/PluginTree';
66

77
use(deepEqualInAnyOrder);
88

9+
/*
10+
* The cleanAST function is used to remove start and end
11+
* properties from ASTs for comparison.
12+
*/
13+
const cleanAST = (node: any) => {
14+
if (typeof node === 'object' && node !== null) {
15+
const { start, end, ...rest } = node;
16+
return Object.keys(rest).reduce((acc, key) => {
17+
acc[key] = Array.isArray(rest[key])
18+
? rest[key].map(cleanAST)
19+
: cleanAST(rest[key]);
20+
return acc;
21+
}, {});
22+
}
23+
return node;
24+
};
25+
926
describe('Plugin Tree', () => {
1027
it('Should parse Plugin', async () => {
11-
const actual = PluginTree.parse(fs.readFileSync(`${__dirname}/fixtures/plugin.idea`, 'utf8'));
12-
const expected = JSON.parse(fs.readFileSync(`${__dirname}/fixtures/plugin.json`, 'utf8'));
28+
const actualRaw = PluginTree.parse(fs.readFileSync(`${__dirname}/fixtures/plugin.idea`, 'utf8'));
29+
const expectedRaw = JSON.parse(fs.readFileSync(`${__dirname}/fixtures/plugin.json`, 'utf8'));
30+
31+
const actual = cleanAST(actualRaw);
32+
const expected = cleanAST(expectedRaw);
1333
//console.log(JSON.stringify(actual, null, 2));
1434
expect(actual).to.deep.equalInAnyOrder(expected);
1535
});
36+
37+
// Line 36
38+
it('Should throw an error when the input code is an empty string', () => {
39+
expect(() => {
40+
const lexerMock = {
41+
expect: (tokenType: string) => { throw new Error('Unexpected end of input'); },
42+
load: () => {}
43+
};
44+
const pluginTree = new PluginTree();
45+
(pluginTree as any)._lexer = lexerMock;
46+
pluginTree.parse('');
47+
}).to.throw(Error, 'Unexpected end of input');
48+
});
49+
1650
});

packages/idea-parser/tests/PropTree.test.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,48 @@ import { describe, it } from 'mocha';
33
import { expect } from 'chai';
44
import PropTree from '../src/trees/PropTree';
55

6+
7+
/*
8+
* The cleanAST function is used to remove start and end
9+
* properties from ASTs for comparison.
10+
*/
11+
const cleanAST = (node: any) => {
12+
if (typeof node === 'object' && node !== null) {
13+
const { start, end, ...rest } = node;
14+
return Object.keys(rest).reduce((acc, key) => {
15+
acc[key] = Array.isArray(rest[key])
16+
? rest[key].map(cleanAST)
17+
: cleanAST(rest[key]);
18+
return acc;
19+
}, {});
20+
}
21+
return node;
22+
};
23+
624
describe('Prop Tree', () => {
725
it('Should parse Prop', async () => {
8-
const actual = PropTree.parse(fs.readFileSync(`${__dirname}/fixtures/prop.idea`, 'utf8'));
9-
const expected = JSON.parse(fs.readFileSync(`${__dirname}/fixtures/prop.json`, 'utf8'));
26+
const actualRaw = PropTree.parse(fs.readFileSync(`${__dirname}/fixtures/prop.idea`, 'utf8'));
27+
const expectedRaw = JSON.parse(fs.readFileSync(`${__dirname}/fixtures/prop.json`, 'utf8'));
28+
29+
const actual = cleanAST(actualRaw);
30+
const expected = cleanAST(expectedRaw);
1031
//console.log(JSON.stringify(actual, null, 2));
1132
expect(actual).to.deep.equalInAnyOrder(expected);
1233
});
34+
35+
// Line 36
36+
it('Should throw an error when the input code is an emty string', () => {
37+
expect(() => {
38+
const lexerMock = {
39+
expect: (tokenType: string) => { throw new Error('Unexpected end of input'); },
40+
load: () => {}
41+
};
42+
const propTree = new PropTree();
43+
(propTree as any)._lexer = lexerMock;
44+
propTree.parse('');
45+
}).to.throw(Error, 'Unexpected end of input');
46+
});
47+
48+
49+
1350
});

0 commit comments

Comments
 (0)