diff --git a/src/ts/tests/comment.spec.ts b/src/ts/tests/comment.spec.ts new file mode 100644 index 00000000..892cf058 --- /dev/null +++ b/src/ts/tests/comment.spec.ts @@ -0,0 +1,134 @@ + +/* eslint-disable max-len */ + +const jssm = require('../../../build/jssm.es5.cjs.js'); + + + + + +describe('block strategies', () => { + + const AtoB = [{"key": "transition", "from": "a", "se": {"kind": "->","to": "b"}}], + + is_AB = str => + test(str, () => expect(jssm.parse(str)).toEqual(AtoB) ), + + ABCD = [{"key": "transition", "from": "a", "se": {"kind": "->","to": "b"}}, + {"key": "transition", "from": "c", "se": {"kind": "->","to": "d"}}], + + is_ABCD = str => + test(str, () => expect(jssm.parse(str)).toEqual(ABCD) ); + + describe('empty block comments in left middle', () => { + is_AB('a/**/->b;'); + is_AB('a /**/->b;'); + is_AB('a/**/ ->b;'); + is_AB('a /**/ ->b;'); + is_AB('a\n/**/->b;'); + is_AB('a/**/\n->b;'); + is_AB('a\n/**/\n->b;'); + }); + + describe('empty block comments in right middle', () => { + is_AB('a->/**/b;'); + is_AB('a-> /**/b;'); + is_AB('a->/**/ b;'); + is_AB('a-> /**/ b;'); + is_AB('a->\n/**/b;'); + is_AB('a->/**/\nb;'); + is_AB('a->\n/**/\nb;'); + }); + + describe('non-empty block comments in left middle', () => { + is_AB('a/* hello */->b;'); + is_AB('a /* hello */->b;'); + is_AB('a/* hello */ ->b;'); + is_AB('a /* hello */ ->b;'); + is_AB('a\n/* hello */ ->b;'); + is_AB('a/* hello */\n->b;'); + is_AB('a\n/* hello */\n->b;'); + }); + + describe('empty block comments before', () => { + is_AB('/**/a->b;'); + is_AB('/**/ a->b;'); + }); + + describe('empty block comments inbetween', () => { + is_ABCD('a->b;/**/c->d;'); + is_ABCD('a->b; /**/c->d;'); + is_ABCD('a->b;/**/ c->d;'); + is_ABCD('a->b; /**/ c->d;'); + }); + + describe('empty block comments after / at end', () => { + is_AB('a->b;/**/'); + is_AB('a->b; /**/'); + }); + + describe('block commented code', () => { + is_AB('a->b;/* c->d; */'); + is_AB('a->b;\n/*c -> d;*/\n'); + is_ABCD('a->b;/* e->f; */c->d;'); + is_ABCD('a->b;\n/*e -> f;*/\nc->d;'); + is_ABCD('a->b;\n/*e -> f;*/\nc->d;\n'); + }); + +}); + + + + + +describe('line strategies', () => { + + const AtoB = [{"key": "transition", "from": "a", "se": {"kind": "->","to": "b"}}], + + is_AB = str => + test(str, () => expect(jssm.parse(str)).toEqual(AtoB) ), + + ABCD = [{"key": "transition", "from": "a", "se": {"kind": "->","to": "b"}}, + {"key": "transition", "from": "c", "se": {"kind": "->","to": "d"}}], + + is_ABCD = str => + test(str, () => expect(jssm.parse(str)).toEqual(ABCD) ); + + describe('empty line comments at end', () => { + is_AB('a->b;//'); + is_AB('a->b; //'); + is_AB('a->b;//\n'); + is_AB('a->b; //\n'); + }); + + describe('non-empty line comments at end', () => { + is_AB('a->b;// hello'); + is_AB('a->b; // hello'); + is_AB('a->b;// hello\n'); + is_AB('a->b; // hello\n'); + }); + + describe('empty line comments at beginning', () => { + is_AB('//\na->b;'); + }); + + describe('non-empty line comments at beginning', () => { + is_AB('// hello\na->b;'); + }); + + describe('empty line comments inbetween', () => { + is_ABCD('a->b;//\nc->d;'); + }); + + describe('non-empty line comments inbetween', () => { + is_ABCD('a->b;// hello\nc->d;'); + }); + + describe('line commented code', () => { + is_AB( 'a->b;// c->d;'); + is_AB( 'a->b;\n//c -> d;\n'); + is_ABCD('a->b;// e->f;\nc->d;'); + is_ABCD('a->b;\n//e -> f;\nc->d;'); + }); + +}); diff --git a/src/ts/tests/compile.spec.ts b/src/ts/tests/compile.spec.ts new file mode 100644 index 00000000..9772f383 --- /dev/null +++ b/src/ts/tests/compile.spec.ts @@ -0,0 +1,76 @@ + +/* eslint-disable max-len */ + +const jssm = require('../jssm'), + sm = jssm.sm; + + + + + +describe('compile/1', () => { + + describe('a->b;', () => { + const a_to_b_str = `a->b;`; + test('doesn\'t throw', () => expect( () => { + jssm.compile(jssm.parse(a_to_b_str)); + }).not.toThrow() ); + }); + + describe('a->b->c;', () => { + const a_to_b_to_c_str = `a->b->c;`; + test('doesn\'t throw', () => expect( () => { + jssm.compile(jssm.parse(a_to_b_to_c_str)); + }).not.toThrow() ); + }); + + describe('template tokens', () => { + const a_through_e_token_str = `a->${'b'}->c->${'d'}->e;`; + test('doesn\'t throw', () => expect( () => { + jssm.compile(jssm.parse(a_through_e_token_str)); + }).not.toThrow() ); + }); + + describe('all arrows', () => { + const all_arrows = `a -> b => c ~> d <-> e <=> f <~> g <-=> h <=-> i <~-> j <-~> k <=~> l <~=> m <- n <= o <~ p;`; + test('doesn\'t throw', () => expect( () => { + jssm.compile(jssm.parse(all_arrows)); + }).not.toThrow() ); + }); + + describe('all unicode arrows', () => { + const all_arrows = `a ← b ⇐ c ↚ d → e ⇒ f ↛ g ↔ h ⇔ i ↮ j ←⇒ k ⇐→ l ←↛ m ↚→ n ⇐↛ o ↚⇒ p;`; + test('doesn\'t throw', () => expect( () => { + jssm.compile(jssm.parse(all_arrows)); + }).not.toThrow() ); + }); + +}); + + + + + +describe('error catchery', () => { + + describe('unknown rule', () => { + test('throws', () => expect( () => { + jssm.compile( [{"key":"FAKE_RULE","from":"a","se":{"kind":"->","to":"b"}}] ); + } ).toThrow() ); + }); + + describe('unnamed state_declaration', () => { + test('throws', () => expect( () => { + jssm.compile( [{"key":"state_declaration"}] ); + } ).toThrow() ); + }); + + describe('unknown state property', () => { + test('throws', () => expect( () => { + sm`a->b; c: { foo: red; };`; + } ).toThrow() ); + }); + +}); + +// stochable diff --git a/src/ts/tests/cycles.js b/src/ts/tests/cycles.js index f6e8c84b..9d5ea929 100644 --- a/src/ts/tests/cycles.js +++ b/src/ts/tests/cycles.js @@ -12,8 +12,10 @@ const jssm = require('../../../build/jssm.es5.cjs.js'); describe('cycle strategies', async _it => { + const is_v = (str, v, it) => it(test, t => t.deepEqual(v, jssm.parse(str))); + describe('basic cycle', async it => { is_v('[a b c] -> +1;', [{from: ['a','b','c'], key: 'transition', se: {kind: '->', to: {key: 'cycle', value: 1}}}], it); }); @@ -68,6 +70,7 @@ describe('cycle strategies', async _it => { is_v('+2 <- [a b c] -> -2;', [{from: {key: 'cycle', value: 2}, key: 'transition', se: {kind: '<-', se: {kind: '->', to: {key: 'cycle', value: -2}}, to: ['a','b','c']}}], it); }); + /* describe('full parse of 2-step cycle', async it => { it('[a b] -> +1;', t => t.deepEqual( @@ -110,10 +113,13 @@ describe('cycle strategies', async _it => { )); }); */ + + describe('illegal fractional cycle throws', async it => { it('throws', t => t.throws( () => { jssm.parse('[a b c] -> +2.5;'); } )); }); + }); diff --git a/src/ts/tests/cycles.spec.ts b/src/ts/tests/cycles.spec.ts new file mode 100644 index 00000000..1d930798 --- /dev/null +++ b/src/ts/tests/cycles.spec.ts @@ -0,0 +1,154 @@ + +/* eslint-disable max-len */ + +const jssm = require('../jssm'); +// sm = jssm.sm; + + + + + +const testdata = [ + + [ + 'basic cycle', + '[a b c] -> +1;', + [{from: ['a','b','c'], key: 'transition', se: {kind: '->', to: {key: 'cycle', value: 1}}}] + ], + + [ + 'negative cycle', + '[a b c] -> -1;', + [{from: ['a','b','c'], key: 'transition', se: {kind: '->', to: {key: 'cycle', value: -1}}}] + ], + + [ + 'nullary cycle', + '[a b c] -> +0;', + [{from: ['a','b','c'], key: 'transition', se: {kind: '->', to: {key: 'cycle', value: 0}}}] + ], + + [ + 'wide cycle', + '[a b c] -> +2;', + [{from: ['a','b','c'], key: 'transition', se: {kind: '->', to: {key: 'cycle', value: 2}}}] + ], + + [ + 'reverse basic cycle', + '+1 <- [a b c];', + [{from: {key: 'cycle', value: 1}, key: 'transition', se: {kind: '<-', to: ['a','b','c']}}] + ], + + [ + 'reverse negative cycle', + '-1 <- [a b c];', + [{from: {key: 'cycle', value: -1}, key: 'transition', se: {kind: '<-', to: ['a','b','c']}}] + ], + + [ + 'reverse nullary cycle', + '+0 <- [a b c];', + [{from: {key: 'cycle', value: 0}, key: 'transition', se: {kind: '<-', to: ['a','b','c']}}] + ], + + [ + 'reverse wide cycle', + '+2 <- [a b c];', + [{from: {key: 'cycle', value: 2}, key: 'transition', se: {kind: '<-', to: ['a','b','c']}}] + ], + + [ + 'bidi basic cycle', + '+1 <- [a b c] -> +1;', + [{from: {key: 'cycle', value: 1}, key: 'transition', se: {kind: '<-', se: {kind: '->', to: {key: 'cycle', value: 1}}, to: ['a','b','c']}}] + ], + + [ + 'bidi negative cycle', + '-1 <- [a b c] -> -1;', + [{from: {key: 'cycle', value: -1}, key: 'transition', se: {kind: '<-', se: {kind: '->', to: {key: 'cycle', value: -1}}, to: ['a','b','c']}}] + ], + + [ + 'bidi basic/negative cycle', + '+1 <- [a b c] -> -1;', + [{from: {key: 'cycle', value: 1}, key: 'transition', se: {kind: '<-', se: {kind: '->', to: {key: 'cycle', value: -1}}, to: ['a','b','c']}}] + ], + + [ + 'bidi nullary cycle', + '+0 <- [a b c] -> +0;', + [{from: {key: 'cycle', value: 0}, key: 'transition', se: {kind: '<-', se: {kind: '->', to: {key: 'cycle', value: 0}}, to: ['a','b','c']}}] + ], + + [ + 'bidi wide cycle', + '+2 <- [a b c] -> -2;', + [{from: {key: 'cycle', value: 2}, key: 'transition', se: {kind: '<-', se: {kind: '->', to: {key: 'cycle', value: -2}}, to: ['a','b','c']}}] + ] + +]; + + + + + +describe('cycle strategies', () => { + + + const is_v = (label, str, v) => + test(`${label} (strategy ${str})`, () => + expect( jssm.parse(str) ).toEqual(v) ); + + testdata.map( ([ label, code, res ]) => is_v(label, code, res) ); + + test.todo('cycle full parses'); + +/* + describe('full parse of 2-step cycle', () => { + it('[a b] -> +1;', t => t.deepEqual( + sm`[a b] -> +1;`.list_edges(), + [{"from":"a","to":"b","kind":"legal","forced_only":false,"main_path":false}, + {"from":"b","to":"a","kind":"legal","forced_only":false,"main_path":false}] + )); + }); + + describe('full parse of 5-step cycle', () => { + it('[a b c d e] -> +1;', t => t.deepEqual( + sm`[a b] -> +1;`.list_edges(), + [{"from":"a","to":"b","kind":"legal","forced_only":false,"main_path":false}, + {"from":"b","to":"c","kind":"legal","forced_only":false,"main_path":false}, + {"from":"c","to":"d","kind":"legal","forced_only":false,"main_path":false}, + {"from":"d","to":"e","kind":"legal","forced_only":false,"main_path":false}, + {"from":"e","to":"a","kind":"legal","forced_only":false,"main_path":false}] + )); + + describe('full parse of 5-step reverse cycle', () => { + it('[a b c d e] -> -1;', t => t.deepEqual( + sm`[a b] -> +1;`.list_edges(), + [{"from":"a","to":"e","kind":"legal","forced_only":false,"main_path":false}, + {"from":"b","to":"a","kind":"legal","forced_only":false,"main_path":false}, + {"from":"c","to":"b","kind":"legal","forced_only":false,"main_path":false}, + {"from":"d","to":"c","kind":"legal","forced_only":false,"main_path":false}, + {"from":"e","to":"d","kind":"legal","forced_only":false,"main_path":false}] + )); + }); + + describe('full parse of 5-step two-step cycle (star)', () => { + it('[a b c d e] -> +2;', t => t.deepEqual( + sm`[a b] -> +1;`.list_edges(), + [{"from":"a","to":"c","kind":"legal","forced_only":false,"main_path":false}, + {"from":"b","to":"d","kind":"legal","forced_only":false,"main_path":false}, + {"from":"c","to":"e","kind":"legal","forced_only":false,"main_path":false}, + {"from":"d","to":"a","kind":"legal","forced_only":false,"main_path":false}, + {"from":"e","to":"b","kind":"legal","forced_only":false,"main_path":false}] + )); + }); +*/ + +}); + +test('illegal fractional cycle throws', () => { + expect( () => jssm.parse('[a b c] -> +2.5;') ).toThrow(); +}); diff --git a/src/ts/tests/dot_preamble.spec.ts b/src/ts/tests/dot_preamble.spec.ts new file mode 100644 index 00000000..a8dd3559 --- /dev/null +++ b/src/ts/tests/dot_preamble.spec.ts @@ -0,0 +1,17 @@ + +const jssm = require('../jssm'), + sm = jssm.sm; + + + + + +describe('Dot preamble', () => { + + test(`doesn't throw`, () => + expect( () => { const _foo = sm`dot_preamble: "x -> y;"; a-> b;`; }).not.toThrow() ); + + test('parses correctly', () => + expect( sm`dot_preamble: "x -> y;"; a-> b;`.dot_preamble() ).toBe('x -> y;') ); + +}); diff --git a/src/ts/tests/flow.spec.ts b/src/ts/tests/flow.spec.ts new file mode 100644 index 00000000..695d8eb0 --- /dev/null +++ b/src/ts/tests/flow.spec.ts @@ -0,0 +1,28 @@ + +import { FlowDirections } from './constants.spec'; + + + + + +const jssm = require('../jssm'), + sm = jssm.sm; + + + + + +describe('Flow directions', () => { + + FlowDirections.map( thisDir => + test(`Direction "${thisDir}" parses as a flow direction`, () => + expect( () => { const _foo = sm`flow: ${thisDir}; a-> b;`; }).not.toThrow() ) ); + + FlowDirections.map( thisDir => + test(`Direction "${thisDir}" parses correctly`, () => + expect( sm`flow: ${thisDir}; a-> b;`.flow() ).toBe(thisDir) ) ); + + test('Fake flow direction throws', () => + expect( () => { const _foo = sm`flow: yourFlowIsWhackSon; a-> b;`; } ).toThrow() ); + +});