Add support for creating and destroying agents during connections
This commit is contained in:
parent
b58506d546
commit
7457131d1e
|
@ -55,6 +55,10 @@ Foo -> Foo: Foo talks to itself
|
|||
Foo -> +Bar: Foo asks Bar
|
||||
-Bar --> Foo: and Bar replies
|
||||
|
||||
# * and ! cause agents to be created and destroyed inline
|
||||
Bar -> *Baz
|
||||
Bar <- !Baz
|
||||
|
||||
# Arrows leaving on the left and right of the diagram
|
||||
[ -> Foo: From the left
|
||||
[ <- Foo: To the left
|
||||
|
@ -258,7 +262,7 @@ Note: the linter can't run from the local filesystem, so you'll need to
|
|||
run a local HTTP server to ensure linting is successful. One option if
|
||||
you have NPM installed is:
|
||||
|
||||
```
|
||||
```shell
|
||||
# Setup
|
||||
npm install http-server -g;
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 53 KiB |
|
@ -18,7 +18,7 @@
|
|||
}
|
||||
|
||||
const SAMPLE_REGEX = new RegExp(
|
||||
/<img src="screenshots\/([^"]*)"[^>]*>[\s]*```([^]+?)```/g
|
||||
/<img src="screenshots\/([^"]*)"[^>]*>[\s]*```(?!shell).*\n([^]+?)```/g
|
||||
);
|
||||
|
||||
function findSamples(content) {
|
||||
|
|
|
@ -46,24 +46,57 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
};
|
||||
}
|
||||
|
||||
const CM_CONNECT = {type: 'keyword', suggest: true, then: {
|
||||
'+': {type: 'operator', suggest: true, then: {'': CM_AGENT_TO_OPTTEXT}},
|
||||
'-': {type: 'operator', suggest: true, then: {'': CM_AGENT_TO_OPTTEXT}},
|
||||
'': CM_AGENT_TO_OPTTEXT,
|
||||
}};
|
||||
function makeCMOperatorBlock(exit) {
|
||||
const op = {type: 'operator', suggest: true, then: {
|
||||
'+': CM_ERROR,
|
||||
'-': CM_ERROR,
|
||||
'*': CM_ERROR,
|
||||
'!': CM_ERROR,
|
||||
'': exit,
|
||||
}};
|
||||
const pm = {type: 'operator', suggest: true, then: {
|
||||
'+': CM_ERROR,
|
||||
'-': CM_ERROR,
|
||||
'*': op,
|
||||
'!': op,
|
||||
'': exit,
|
||||
}};
|
||||
const se = {type: 'operator', suggest: true, then: {
|
||||
'+': op,
|
||||
'-': op,
|
||||
'*': CM_ERROR,
|
||||
'!': CM_ERROR,
|
||||
'': exit,
|
||||
}};
|
||||
return {
|
||||
'+': pm,
|
||||
'-': pm,
|
||||
'*': se,
|
||||
'!': se,
|
||||
'': exit,
|
||||
};
|
||||
}
|
||||
|
||||
const CM_CONNECT_FULL = {type: 'variable', suggest: 'Agent', then: {
|
||||
'->': CM_CONNECT,
|
||||
'-->': CM_CONNECT,
|
||||
'<-': CM_CONNECT,
|
||||
'<--': CM_CONNECT,
|
||||
'<->': CM_CONNECT,
|
||||
'<-->': CM_CONNECT,
|
||||
':': {type: 'operator', suggest: true, override: 'Label', then: {}},
|
||||
'': 0,
|
||||
}};
|
||||
function makeCMConnect() {
|
||||
const connect = {
|
||||
type: 'keyword',
|
||||
suggest: true,
|
||||
then: makeCMOperatorBlock(CM_AGENT_TO_OPTTEXT),
|
||||
};
|
||||
|
||||
const CM_COMMANDS = {type: 'error line-error', then: {
|
||||
return makeCMOperatorBlock({type: 'variable', suggest: 'Agent', then: {
|
||||
'->': connect,
|
||||
'-->': connect,
|
||||
'<-': connect,
|
||||
'<--': connect,
|
||||
'<->': connect,
|
||||
'<-->': connect,
|
||||
':': {type: 'operator', suggest: true, override: 'Label', then: {}},
|
||||
'': 0,
|
||||
}});
|
||||
}
|
||||
|
||||
const CM_COMMANDS = {type: 'error line-error', then: Object.assign({
|
||||
'title': {type: 'keyword', suggest: true, then: {
|
||||
'': CM_TEXT_TO_END,
|
||||
}},
|
||||
|
@ -134,10 +167,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
}},
|
||||
}},
|
||||
}},
|
||||
'+': {type: 'operator', suggest: true, then: {'': CM_CONNECT_FULL}},
|
||||
'-': {type: 'operator', suggest: true, then: {'': CM_CONNECT_FULL}},
|
||||
'': CM_CONNECT_FULL,
|
||||
}};
|
||||
}, makeCMConnect())};
|
||||
|
||||
function cmCappedToken(token, current) {
|
||||
if(Object.keys(current.then).length > 0) {
|
||||
|
|
|
@ -25,8 +25,8 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
return agent.name;
|
||||
}
|
||||
|
||||
function agentHasFlag(flag) {
|
||||
return (agent) => agent.flags.includes(flag);
|
||||
function agentHasFlag(flag, has = true) {
|
||||
return (agent) => (agent.flags.includes(flag) === has);
|
||||
}
|
||||
|
||||
const MERGABLE = {
|
||||
|
@ -235,7 +235,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
array.mergeSets(this.agents, agents, agentEqCheck);
|
||||
}
|
||||
|
||||
setAgentVis(agents, visible, mode, checked = false) {
|
||||
setAgentVisRaw(agents, visible, mode, checked = false) {
|
||||
const filteredAgents = agents.filter((agent) => {
|
||||
const state = this.agentStates.get(agent.name) || DEFAULT_AGENT;
|
||||
if(state.locked) {
|
||||
|
@ -269,6 +269,15 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
};
|
||||
}
|
||||
|
||||
setAgentVis(agents, visible, mode, checked = false) {
|
||||
return this.setAgentVisRaw(
|
||||
agents.map(convertAgent),
|
||||
visible,
|
||||
mode,
|
||||
checked
|
||||
);
|
||||
}
|
||||
|
||||
setAgentHighlight(agents, highlighted, checked = false) {
|
||||
const filteredAgents = agents.filter((agent) => {
|
||||
const state = this.agentStates.get(agent.name) || DEFAULT_AGENT;
|
||||
|
@ -340,15 +349,24 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
}
|
||||
|
||||
handleConnect({agents, label, options}) {
|
||||
const colAgents = agents.map(convertAgent);
|
||||
this.addStage(this.setAgentVis(colAgents, true, 'box'));
|
||||
this.defineAgents(colAgents);
|
||||
const beginAgents = agents.filter(agentHasFlag('begin'));
|
||||
const endAgents = agents.filter(agentHasFlag('end'));
|
||||
if(array.hasIntersection(beginAgents, endAgents, agentEqCheck)) {
|
||||
throw new Error('Cannot set agent visibility multiple times');
|
||||
}
|
||||
|
||||
const startAgents = agents.filter(agentHasFlag('start'));
|
||||
const stopAgents = agents.filter(agentHasFlag('stop'));
|
||||
array.mergeSets(stopAgents, endAgents);
|
||||
if(array.hasIntersection(startAgents, stopAgents, agentEqCheck)) {
|
||||
throw new Error('Cannot set agent highlighting multiple times');
|
||||
}
|
||||
|
||||
this.defineAgents(agents.map(convertAgent));
|
||||
|
||||
const implicitBegin = agents.filter(agentHasFlag('begin', false));
|
||||
this.addStage(this.setAgentVis(implicitBegin, true, 'box'));
|
||||
|
||||
const connectStage = {
|
||||
type: 'connect',
|
||||
agentNames: agents.map(getAgentName),
|
||||
|
@ -357,9 +375,11 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
};
|
||||
|
||||
this.addParallelStages([
|
||||
this.setAgentVis(beginAgents, true, 'box', true),
|
||||
this.setAgentHighlight(startAgents, true, true),
|
||||
connectStage,
|
||||
this.setAgentHighlight(stopAgents, false, true),
|
||||
this.setAgentVis(endAgents, false, 'cross', true),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -371,7 +391,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
colAgents = agents.map(convertAgent);
|
||||
}
|
||||
|
||||
this.addStage(this.setAgentVis(colAgents, true, 'box'));
|
||||
this.addStage(this.setAgentVisRaw(colAgents, true, 'box'));
|
||||
this.defineAgents(colAgents);
|
||||
|
||||
this.addStage({
|
||||
|
@ -383,23 +403,17 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
}
|
||||
|
||||
handleAgentDefine({agents}) {
|
||||
const colAgents = agents.map(convertAgent);
|
||||
this.defineAgents(colAgents);
|
||||
this.defineAgents(agents.map(convertAgent));
|
||||
}
|
||||
|
||||
handleAgentBegin({agents, mode}) {
|
||||
this.addStage(this.setAgentVis(
|
||||
agents.map(convertAgent),
|
||||
true,
|
||||
mode,
|
||||
true
|
||||
));
|
||||
this.addStage(this.setAgentVis(agents, true, mode, true));
|
||||
}
|
||||
|
||||
handleAgentEnd({agents, mode}) {
|
||||
this.addParallelStages([
|
||||
this.setAgentHighlight(agents, false),
|
||||
this.setAgentVis(agents.map(convertAgent), false, mode, true),
|
||||
this.setAgentVis(agents, false, mode, true),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -471,7 +485,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
|
||||
this.addParallelStages([
|
||||
this.setAgentHighlight(this.agents, false),
|
||||
this.setAgentVis(this.agents, false, terminators),
|
||||
this.setAgentVisRaw(this.agents, false, terminators),
|
||||
]);
|
||||
|
||||
addBounds(
|
||||
|
|
|
@ -426,6 +426,51 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('adds parallel begin stages', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['begin']}]),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
GENERATED.beginAgents(['A']),
|
||||
GENERATED.parallel([
|
||||
GENERATED.beginAgents(['B']),
|
||||
GENERATED.connect(['A', 'B']),
|
||||
]),
|
||||
GENERATED.endAgents(['A', 'B']),
|
||||
]);
|
||||
});
|
||||
|
||||
it('adds parallel end stages', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['end']}]),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
GENERATED.beginAgents(['A', 'B']),
|
||||
GENERATED.parallel([
|
||||
GENERATED.connect(['A', 'B']),
|
||||
GENERATED.endAgents(['B']),
|
||||
]),
|
||||
GENERATED.endAgents(['A']),
|
||||
]);
|
||||
});
|
||||
|
||||
it('implicitly ends highlighting when ending a stage', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['start']}]),
|
||||
PARSED.connect(['A', {name: 'B', flags: ['end']}]),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
jasmine.anything(),
|
||||
jasmine.anything(),
|
||||
GENERATED.parallel([
|
||||
GENERATED.connect(['A', 'B']),
|
||||
GENERATED.highlight(['B'], false),
|
||||
GENERATED.endAgents(['B']),
|
||||
]),
|
||||
GENERATED.endAgents(['A']),
|
||||
]);
|
||||
});
|
||||
|
||||
it('rejects conflicting flags', () => {
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['start', 'stop']}]),
|
||||
|
@ -437,6 +482,17 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
{name: 'A', flags: ['stop']},
|
||||
]),
|
||||
]})).toThrow();
|
||||
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.connect(['A', {name: 'B', flags: ['begin', 'end']}]),
|
||||
]})).toThrow();
|
||||
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.connect([
|
||||
{name: 'A', flags: ['begin']},
|
||||
{name: 'A', flags: ['end']},
|
||||
]),
|
||||
]})).toThrow();
|
||||
});
|
||||
|
||||
it('adds implicit highlight end with implicit terminator', () => {
|
||||
|
|
|
@ -26,8 +26,10 @@ define([
|
|||
};
|
||||
|
||||
const CONNECT_AGENT_FLAGS = {
|
||||
'*': 'begin',
|
||||
'+': 'start',
|
||||
'-': 'stop',
|
||||
'!': 'end',
|
||||
};
|
||||
|
||||
const TERMINATOR_TYPES = [
|
||||
|
@ -116,13 +118,20 @@ define([
|
|||
const flags = [];
|
||||
let p = start;
|
||||
for(; p < end; ++ p) {
|
||||
const flag = flagTypes[tokenKeyword(line[p])];
|
||||
const rawFlag = tokenKeyword(line[p]);
|
||||
const flag = flagTypes[rawFlag];
|
||||
if(flag) {
|
||||
if(flags.includes(flag)) {
|
||||
throw new Error('Duplicate agent flag: ' + rawFlag);
|
||||
}
|
||||
flags.push(flag);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(p >= end) {
|
||||
throw new Error('Missing agent name');
|
||||
}
|
||||
return {
|
||||
name: joinLabel(line, p, end),
|
||||
flags,
|
||||
|
|
|
@ -79,13 +79,13 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
});
|
||||
|
||||
it('parses optional flags', () => {
|
||||
const parsed = parser.parse('+A -> -B');
|
||||
const parsed = parser.parse('+A -> -*!B');
|
||||
expect(parsed.stages).toEqual([
|
||||
{
|
||||
type: 'connect',
|
||||
agents: [
|
||||
{name: 'A', flags: ['start']},
|
||||
{name: 'B', flags: ['stop']},
|
||||
{name: 'B', flags: ['stop', 'begin', 'end']},
|
||||
],
|
||||
label: jasmine.anything(),
|
||||
options: jasmine.anything(),
|
||||
|
@ -93,6 +93,15 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('rejects duplicate flags', () => {
|
||||
expect(() => parser.parse('A -> +*+B')).toThrow();
|
||||
expect(() => parser.parse('A -> **B')).toThrow();
|
||||
});
|
||||
|
||||
it('rejects missing agent names', () => {
|
||||
expect(() => parser.parse('A -> +')).toThrow();
|
||||
});
|
||||
|
||||
it('converts multiple entries', () => {
|
||||
const parsed = parser.parse('A -> B\nB -> A');
|
||||
expect(parsed.stages).toEqual([
|
||||
|
|
|
@ -161,10 +161,10 @@ define([
|
|||
const agentSpaces = new Map();
|
||||
const agentNames = this.visibleAgents.slice();
|
||||
|
||||
const addSpacing = (agentName, spacing) => {
|
||||
const addSpacing = (agentName, {left, right}) => {
|
||||
const current = agentSpaces.get(agentName);
|
||||
current.left = Math.max(current.left, spacing.left);
|
||||
current.right = Math.max(current.right, spacing.right);
|
||||
current.left = Math.max(current.left, left);
|
||||
current.right = Math.max(current.right, right);
|
||||
};
|
||||
|
||||
this.agentInfos.forEach((agentInfo) => {
|
||||
|
@ -366,7 +366,7 @@ define([
|
|||
const touchedAgentNames = [];
|
||||
stages.forEach((stage) => {
|
||||
const component = this.components.get(stage.type);
|
||||
const r = component.renderPre(stage, envPre);
|
||||
const r = component.renderPre(stage, envPre) || {};
|
||||
if(r.topShift !== undefined) {
|
||||
maxTopShift = Math.max(maxTopShift, r.topShift);
|
||||
}
|
||||
|
@ -460,6 +460,7 @@ define([
|
|||
x: null,
|
||||
latestYStart: null,
|
||||
currentRad: 0,
|
||||
currentMaxRad: 0,
|
||||
latestY: 0,
|
||||
maxRPad: 0,
|
||||
maxLPad: 0,
|
||||
|
|
|
@ -31,10 +31,13 @@ define(['./CodeMirrorMode'], (CMMode) => {
|
|||
unescape,
|
||||
baseToken: {q: true},
|
||||
},
|
||||
{start: /(?=[^ \t\r\n:+\-<>,])/y, end: /(?=[ \t\r\n:+\-<>,])|$/y},
|
||||
{start: /(?=[+\-<>])/y, end: /(?=[^+\-<>])|$/y},
|
||||
{start: /(?=[^ \t\r\n:+\-*!<>,])/y, end: /(?=[ \t\r\n:+\-*!<>,])|$/y},
|
||||
{start: /(?=[\-<>])/y, end: /(?=[^\-<>])|$/y},
|
||||
{start: /,/y, baseToken: {v: ','}},
|
||||
{start: /:/y, baseToken: {v: ':'}},
|
||||
{start: /!/y, baseToken: {v: '!'}},
|
||||
{start: /\+/y, baseToken: {v: '+'}},
|
||||
{start: /\*/y, baseToken: {v: '*'}},
|
||||
{start: /\n/y, baseToken: {v: '\n'}},
|
||||
];
|
||||
|
||||
|
|
|
@ -23,11 +23,18 @@ define([
|
|||
return {
|
||||
left: width / 2,
|
||||
right: width / 2,
|
||||
radius: width / 2,
|
||||
};
|
||||
}
|
||||
|
||||
topShift() {
|
||||
return 0;
|
||||
topShift({label}, env) {
|
||||
const config = env.theme.agentCap.box;
|
||||
const height = (
|
||||
env.textSizer.measureHeight(config.labelAttrs, label) +
|
||||
config.padding.top +
|
||||
config.padding.bottom
|
||||
);
|
||||
return Math.max(0, height - config.arrowBottom);
|
||||
}
|
||||
|
||||
render(y, {x, label}, env) {
|
||||
|
@ -57,6 +64,7 @@ define([
|
|||
return {
|
||||
left: config.size / 2,
|
||||
right: config.size / 2,
|
||||
radius: 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -98,6 +106,7 @@ define([
|
|||
return {
|
||||
left: width / 2,
|
||||
right: width / 2,
|
||||
radius: width / 2,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -131,7 +140,11 @@ define([
|
|||
|
||||
class CapNone {
|
||||
separation({currentRad}) {
|
||||
return {left: currentRad, right: currentRad};
|
||||
return {
|
||||
left: currentRad,
|
||||
right: currentRad,
|
||||
radius: currentRad,
|
||||
};
|
||||
}
|
||||
|
||||
topShift(agentInfo, env) {
|
||||
|
@ -162,18 +175,24 @@ define([
|
|||
this.begin = begin;
|
||||
}
|
||||
|
||||
separationPre({mode, agentNames}, env) {
|
||||
agentNames.forEach((name) => {
|
||||
const agentInfo = env.agentInfos.get(name);
|
||||
const sep = AGENT_CAPS[mode].separation(agentInfo, env);
|
||||
env.addSpacing(name, sep);
|
||||
agentInfo.currentMaxRad = Math.max(
|
||||
agentInfo.currentMaxRad,
|
||||
sep.radius
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
separation({mode, agentNames}, env) {
|
||||
if(this.begin) {
|
||||
array.mergeSets(env.visibleAgents, agentNames);
|
||||
} else {
|
||||
array.removeAll(env.visibleAgents, agentNames);
|
||||
}
|
||||
|
||||
agentNames.forEach((name) => {
|
||||
const agentInfo = env.agentInfos.get(name);
|
||||
const separationFn = AGENT_CAPS[mode].separation;
|
||||
env.addSpacing(name, separationFn(agentInfo, env));
|
||||
});
|
||||
}
|
||||
|
||||
renderPre({mode, agentNames}, env) {
|
||||
|
@ -182,6 +201,9 @@ define([
|
|||
const agentInfo = env.agentInfos.get(name);
|
||||
const topShift = AGENT_CAPS[mode].topShift(agentInfo, env);
|
||||
maxTopShift = Math.max(maxTopShift, topShift);
|
||||
|
||||
const r = AGENT_CAPS[mode].separation(agentInfo, env).radius;
|
||||
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
||||
});
|
||||
return {
|
||||
agentNames,
|
||||
|
|
|
@ -2,21 +2,32 @@ define(['./BaseComponent'], (BaseComponent) => {
|
|||
'use strict';
|
||||
|
||||
class AgentHighlight extends BaseComponent {
|
||||
radius(highlighted, env) {
|
||||
return highlighted ? env.theme.agentLineHighlightRadius : 0;
|
||||
}
|
||||
|
||||
separationPre({agentNames, highlighted}, env) {
|
||||
const rad = highlighted ? env.theme.agentLineHighlightRadius : 0;
|
||||
const r = this.radius(highlighted, env);
|
||||
agentNames.forEach((name) => {
|
||||
const agentInfo = env.agentInfos.get(name);
|
||||
const maxRad = Math.max(agentInfo.currentMaxRad, rad);
|
||||
agentInfo.currentRad = rad;
|
||||
agentInfo.currentMaxRad = maxRad;
|
||||
agentInfo.currentRad = r;
|
||||
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
||||
});
|
||||
}
|
||||
|
||||
renderPre({agentNames, highlighted}, env) {
|
||||
const r = this.radius(highlighted, env);
|
||||
agentNames.forEach((name) => {
|
||||
const agentInfo = env.agentInfos.get(name);
|
||||
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
||||
});
|
||||
}
|
||||
|
||||
render({agentNames, highlighted}, env) {
|
||||
const rad = highlighted ? env.theme.agentLineHighlightRadius : 0;
|
||||
const r = this.radius(highlighted, env);
|
||||
agentNames.forEach((name) => {
|
||||
env.drawAgentLine(name, env.primaryY);
|
||||
env.agentInfos.get(name).currentRad = rad;
|
||||
env.agentInfos.get(name).currentRad = r;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ define(() => {
|
|||
textSizer,
|
||||
state,
|
||||
}*/) {
|
||||
return {};
|
||||
}
|
||||
|
||||
render(/*stage, {
|
||||
|
@ -49,7 +48,6 @@ define(() => {
|
|||
SVGTextBlockClass,
|
||||
state,
|
||||
}*/) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ define([
|
|||
config.label.margin.bottom
|
||||
);
|
||||
|
||||
const lineX = from.x + from.currentRad;
|
||||
const lineX = from.x + from.currentMaxRad;
|
||||
const y0 = env.primaryY;
|
||||
const x0 = (
|
||||
lineX +
|
||||
|
@ -159,8 +159,8 @@ define([
|
|||
config.label.margin.bottom
|
||||
);
|
||||
|
||||
const x0 = from.x + from.currentRad * dir;
|
||||
const x1 = to.x - to.currentRad * dir;
|
||||
const x0 = from.x + from.currentMaxRad * dir;
|
||||
const x1 = to.x - to.currentMaxRad * dir;
|
||||
let y = env.primaryY;
|
||||
|
||||
SVGShapes.renderBoxedText(label, {
|
||||
|
|
|
@ -131,8 +131,8 @@ define(['./BaseComponent'], (BaseComponent) => {
|
|||
const infoL = env.agentInfos.get(left);
|
||||
const infoR = env.agentInfos.get(right);
|
||||
return this.renderNote({
|
||||
x0: infoL.x - infoL.currentRad - config.overlap.left,
|
||||
x1: infoR.x + infoR.currentRad + config.overlap.right,
|
||||
x0: infoL.x - infoL.currentMaxRad - config.overlap.left,
|
||||
x1: infoR.x + infoR.currentMaxRad + config.overlap.right,
|
||||
anchor: 'middle',
|
||||
mode,
|
||||
label,
|
||||
|
@ -186,7 +186,7 @@ define(['./BaseComponent'], (BaseComponent) => {
|
|||
const {left, right} = findExtremes(env.agentInfos, agentNames);
|
||||
if(this.isRight) {
|
||||
const info = env.agentInfos.get(right);
|
||||
const x0 = info.x + info.currentRad + config.margin.left;
|
||||
const x0 = info.x + info.currentMaxRad + config.margin.left;
|
||||
return this.renderNote({
|
||||
x0,
|
||||
anchor: 'start',
|
||||
|
@ -195,7 +195,7 @@ define(['./BaseComponent'], (BaseComponent) => {
|
|||
}, env);
|
||||
} else {
|
||||
const info = env.agentInfos.get(left);
|
||||
const x1 = info.x - info.currentRad - config.margin.right;
|
||||
const x1 = info.x - info.currentMaxRad - config.margin.right;
|
||||
return this.renderNote({
|
||||
x1,
|
||||
anchor: 'end',
|
||||
|
@ -232,8 +232,8 @@ define(['./BaseComponent'], (BaseComponent) => {
|
|||
const infoL = env.agentInfos.get(left);
|
||||
const infoR = env.agentInfos.get(right);
|
||||
const xMid = (
|
||||
infoL.x + infoL.currentRad +
|
||||
infoR.x - infoR.currentRad
|
||||
infoL.x + infoL.currentMaxRad +
|
||||
infoR.x - infoR.currentMaxRad
|
||||
) / 2;
|
||||
|
||||
return this.renderNote({
|
||||
|
|
|
@ -108,176 +108,32 @@ defineDescribe('Sequence Integration', [
|
|||
);
|
||||
});
|
||||
|
||||
it('Renders the "Simple Usage" example without error', () => {
|
||||
const parsed = parser.parse(
|
||||
'title Labyrinth\n' +
|
||||
'\n' +
|
||||
'Bowie -> Gremlin: You remind me of the babe\n' +
|
||||
'Gremlin -> Bowie: What babe?\n' +
|
||||
'Bowie -> Gremlin: The babe with the power\n' +
|
||||
'Gremlin -> Bowie: What power?\n' +
|
||||
'note right of Bowie, Gremlin: Most people get muddled here!\n' +
|
||||
'Bowie -> Gremlin: \'The power of voodoo\'\n' +
|
||||
'Gremlin -> Bowie: "Who-do?"\n' +
|
||||
'Bowie -> Gremlin: You do!\n' +
|
||||
'Gremlin -> Bowie: Do what?\n' +
|
||||
'Bowie -> Gremlin: Remind me of the babe!\n' +
|
||||
'\n' +
|
||||
'Bowie -> Audience: Sings\n' +
|
||||
'\n' +
|
||||
'terminators box\n'
|
||||
);
|
||||
const sequence = generator.generate(parsed);
|
||||
expect(() => renderer.render(sequence)).not.toThrow();
|
||||
});
|
||||
const SAMPLE_REGEX = new RegExp(
|
||||
/```(?!shell).*\n([^]+?)```/g
|
||||
);
|
||||
|
||||
it('Renders the "Connection Types" example without error', () => {
|
||||
const parsed = parser.parse(
|
||||
'title Connection Types\n' +
|
||||
'\n' +
|
||||
'Foo -> Bar: Simple arrow\n' +
|
||||
'Foo --> Bar: Dashed arrow\n' +
|
||||
'Foo <- Bar: Reversed arrow\n' +
|
||||
'Foo <-- Bar: Reversed dashed arrow\n' +
|
||||
'Foo <-> Bar: Double arrow\n' +
|
||||
'Foo <--> Bar: Double dashed arrow\n' +
|
||||
'\n' +
|
||||
'# An arrow with no label:\n' +
|
||||
'Foo -> Bar\n' +
|
||||
'\n' +
|
||||
'Foo -> Foo: Foo talks to itself\n' +
|
||||
'\n' +
|
||||
'# Arrows leaving on the left and right of the diagram\n' +
|
||||
'[ -> Foo: From the left\n' +
|
||||
'[ <- Foo: To the left\n' +
|
||||
'Foo -> ]: To the right\n' +
|
||||
'Foo <- ]: From the right\n' +
|
||||
'[ -> ]: Left to right!\n' +
|
||||
'# (etc.)\n'
|
||||
);
|
||||
const sequence = generator.generate(parsed);
|
||||
expect(() => renderer.render(sequence)).not.toThrow();
|
||||
});
|
||||
function findSamples(content) {
|
||||
SAMPLE_REGEX.lastIndex = 0;
|
||||
const results = [];
|
||||
while(true) {
|
||||
const match = SAMPLE_REGEX.exec(content);
|
||||
if(!match) {
|
||||
break;
|
||||
}
|
||||
results.push(match[1]);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
it('Renders the "Notes & State" example without error', () => {
|
||||
const parsed = parser.parse(
|
||||
'title Note Placements\n' +
|
||||
'\n' +
|
||||
'note over Foo: Foo says something\n' +
|
||||
'note left of Foo: Stuff\n' +
|
||||
'note right of Bar: More stuff\n' +
|
||||
'note over Foo, Bar: "Foo and Bar\n' +
|
||||
'on multiple lines"\n' +
|
||||
'note between Foo, Bar: Link\n' +
|
||||
'\n' +
|
||||
'text right: \'Comments\\nOver here\!\'\n' +
|
||||
'\n' +
|
||||
'state over Foo: Foo is ponderous'
|
||||
);
|
||||
const sequence = generator.generate(parsed);
|
||||
expect(() => renderer.render(sequence)).not.toThrow();
|
||||
});
|
||||
|
||||
it('Renders the "Logic" example without error', () => {
|
||||
const parsed = parser.parse(
|
||||
'title At the Bank\n' +
|
||||
'\n' +
|
||||
'begin Person, ATM, Bank\n' +
|
||||
'Person -> ATM: Request money\n' +
|
||||
'ATM -> Bank: Check funds\n' +
|
||||
'if fraud detected\n' +
|
||||
' Bank -> Police: "Get \'em!"\n' +
|
||||
' Police -> Person: "You\'re nicked"\n' +
|
||||
' end Police\n' +
|
||||
'else if sufficient funds\n' +
|
||||
' ATM -> Bank: Withdraw funds\n' +
|
||||
' repeat until "all requested money\n' +
|
||||
' has been handed over"\n' +
|
||||
' ATM -> Person: Dispense note\n' +
|
||||
' end\n' +
|
||||
'else\n' +
|
||||
' ATM -> Person: Error\n' +
|
||||
'end'
|
||||
);
|
||||
const sequence = generator.generate(parsed);
|
||||
expect(() => renderer.render(sequence)).not.toThrow();
|
||||
});
|
||||
|
||||
it('Renders the "Multiline Text" example without error', () => {
|
||||
const parsed = parser.parse(
|
||||
'title \'My Multiline\n' +
|
||||
'Title\'\n' +
|
||||
'\n' +
|
||||
'note over Foo: \'Also possible\\nwith escapes\'\n' +
|
||||
'\n' +
|
||||
'Foo -> Bar: \'Lines of text\\non this arrow\'\n' +
|
||||
'\n' +
|
||||
'if \'Even multiline\\ninside conditions like this\'\n' +
|
||||
' Foo -> \'Multiline\\nagent\'\n' +
|
||||
'end\n' +
|
||||
'\n' +
|
||||
'state over Foo: \'Newlines here,\\ntoo!\''
|
||||
);
|
||||
const sequence = generator.generate(parsed);
|
||||
expect(() => renderer.render(sequence)).not.toThrow();
|
||||
});
|
||||
|
||||
it('Renders the "Short-Lived Agents" example without error', () => {
|
||||
const parsed = parser.parse(
|
||||
'title "Baz doesn\'t live long"\n' +
|
||||
'\n' +
|
||||
'Foo -> Bar\n' +
|
||||
'begin Baz\n' +
|
||||
'Bar -> Baz\n' +
|
||||
'Baz -> Foo\n' +
|
||||
'end Baz\n' +
|
||||
'Foo -> Bar\n' +
|
||||
'\n' +
|
||||
'# Foo and Bar end with black bars\n' +
|
||||
'terminators bar\n' +
|
||||
'# (options are: box, bar, cross, none)'
|
||||
);
|
||||
const sequence = generator.generate(parsed);
|
||||
expect(() => renderer.render(sequence)).not.toThrow();
|
||||
});
|
||||
|
||||
it('Renders the "Alternative Agent Ordering" example without error', () => {
|
||||
const parsed = parser.parse(
|
||||
'define Baz, Foo\n' +
|
||||
'Foo -> Bar\n' +
|
||||
'Bar -> Baz\n'
|
||||
);
|
||||
const sequence = generator.generate(parsed);
|
||||
expect(() => renderer.render(sequence)).not.toThrow();
|
||||
});
|
||||
|
||||
it('Renders the "Simultaneous Actions" example without error', () => {
|
||||
const parsed = parser.parse(
|
||||
'begin A, B, C, D\n' +
|
||||
'A -> C\n' +
|
||||
'\n' +
|
||||
'# Define a marker which can be returned to later\n' +
|
||||
'\n' +
|
||||
'some primary process:\n' +
|
||||
'A -> B\n' +
|
||||
'B -> A\n' +
|
||||
'A -> B\n' +
|
||||
'B -> A\n' +
|
||||
'\n' +
|
||||
'# Return to the defined marker\n' +
|
||||
'# (should be interpreted as no-higher-then the marker; may be\n' +
|
||||
'# pushed down to keep relative action ordering consistent)\n' +
|
||||
'\n' +
|
||||
'simultaneously with some primary process:\n' +
|
||||
'C -> D\n' +
|
||||
'D -> C\n' +
|
||||
'end D\n' +
|
||||
'C -> A\n' +
|
||||
'\n' +
|
||||
'# The marker name is optional; using "simultaneously:" with no\n' +
|
||||
'# marker will jump to the top of the entire sequence.'
|
||||
);
|
||||
const sequence = generator.generate(parsed);
|
||||
expect(() => renderer.render(sequence)).not.toThrow();
|
||||
});
|
||||
return (fetch('README.md')
|
||||
.then((response) => response.text())
|
||||
.then(findSamples)
|
||||
.then((samples) => samples.forEach((code, i) => {
|
||||
it('Renders readme example #' + (i + 1) + ' without error', () => {
|
||||
const parsed = parser.parse(code);
|
||||
const sequence = generator.generate(parsed);
|
||||
expect(() => renderer.render(sequence)).not.toThrow();
|
||||
});
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
|
|||
right: 10,
|
||||
bottom: 5,
|
||||
},
|
||||
arrowBottom: 5 + 12 * 1.3 / 2,
|
||||
boxAttrs: {
|
||||
'fill': '#FFFFFF',
|
||||
'stroke': '#000000',
|
||||
|
|
Loading…
Reference in New Issue