Agents as objects after generate

This commit is contained in:
David Evans 2017-11-02 22:14:44 +00:00
parent 1016f9aac0
commit 2d8e3d60e1
7 changed files with 683 additions and 416 deletions

View File

@ -1,31 +1,43 @@
define(() => {
'use strict';
function mergeSets(target, b = null) {
function indexOf(list, element, equalityCheck = null) {
if(equalityCheck === null) {
return list.indexOf(element);
}
for(let i = 0; i < list.length; ++ i) {
if(equalityCheck(list[i], element)) {
return i;
}
}
return -1;
}
function mergeSets(target, b = null, equalityCheck = null) {
if(!b) {
return;
}
for(let i = 0; i < b.length; ++ i) {
if(target.indexOf(b[i]) === -1) {
if(indexOf(target, b[i], equalityCheck) === -1) {
target.push(b[i]);
}
}
}
function removeAll(target, b = null) {
function removeAll(target, b = null, equalityCheck = null) {
if(!b) {
return;
}
for(let i = 0; i < b.length; ++ i) {
const p = target.indexOf(b[i]);
const p = indexOf(target, b[i], equalityCheck);
if(p !== -1) {
target.splice(p, 1);
}
}
}
function remove(list, item) {
const p = list.indexOf(item);
function remove(list, item, equalityCheck = null) {
const p = indexOf(list, item, equalityCheck);
if(p !== -1) {
list.splice(p, 1);
}
@ -36,6 +48,7 @@ define(() => {
}
return {
indexOf,
mergeSets,
removeAll,
remove,

View File

@ -35,6 +35,17 @@ defineDescribe('ArrayUtilities', ['./ArrayUtilities'], (array) => {
array.mergeSets(p1, p2);
expect(p1).toEqual(['a', 'x', 'c', 'd', 'e']);
});
it('uses the given equality check function', () => {
const p1 = ['a', 'b', 'c', 'd'];
const p2 = ['b', 'B', 'E', 'e'];
array.mergeSets(
p1,
p2,
(a, b) => (a.toLowerCase() === b.toLowerCase())
);
expect(p1).toEqual(['a', 'b', 'c', 'd', 'E']);
});
});
describe('.removeAll', () => {
@ -71,32 +82,53 @@ defineDescribe('ArrayUtilities', ['./ArrayUtilities'], (array) => {
array.removeAll(p1, p2);
expect(p1).toEqual(['a', 'x', 'd']);
});
it('uses the given equality check function', () => {
const p1 = ['a', 'b', 'c', 'd'];
const p2 = ['B', 'e'];
array.removeAll(
p1,
p2,
(a, b) => (a.toLowerCase() === b.toLowerCase())
);
expect(p1).toEqual(['a', 'c', 'd']);
});
});
describe('.remove', () => {
it('removes one element matching the parameter', () => {
const p1 = ['a', 'b'];
array.removeAll(p1, 'b');
array.remove(p1, 'b');
expect(p1).toEqual(['a']);
});
it('removes only the first element matching the parameter', () => {
const p1 = ['a', 'b', 'c', 'b'];
array.removeAll(p1, 'b');
array.remove(p1, 'b');
expect(p1).toEqual(['a', 'c', 'b']);
});
it('ignores if not found', () => {
const p1 = ['a', 'b', 'c'];
array.removeAll(p1, 'nope');
array.remove(p1, 'nope');
expect(p1).toEqual(['a', 'b', 'c']);
});
it('maintains input ordering', () => {
const p1 = ['a', 'b', 'c'];
array.removeAll(p1, 'b');
array.remove(p1, 'b');
expect(p1).toEqual(['a', 'c']);
});
it('uses the given equality check function', () => {
const p1 = ['a', 'b', 'c', 'd'];
array.remove(
p1,
'B',
(a, b) => (a.toLowerCase() === b.toLowerCase())
);
expect(p1).toEqual(['a', 'c', 'd']);
});
});
describe('.last', () => {

View File

@ -8,6 +8,22 @@ define(['core/ArrayUtilities'], (array) => {
}
}
function agentEqCheck(a, b) {
return a.name === b.name;
}
function makeAgent(name, {anchorRight = false} = {}) {
return {name, anchorRight};
}
function convertAgent(agent) {
return makeAgent(agent.name);
}
function getAgentName(agent) {
return agent.name;
}
const LOCKED_AGENT = new AgentState(false, true);
const DEFAULT_AGENT = new AgentState(false);
@ -30,12 +46,14 @@ define(['core/ArrayUtilities'], (array) => {
this.stageHandlers = {
'mark': this.handleMark.bind(this),
'async': this.handleAsync.bind(this),
'note over': this.handleNote.bind(this),
'note left': this.handleNote.bind(this),
'note right': this.handleNote.bind(this),
'agent define': this.handleAgentDefine.bind(this),
'agent begin': this.handleAgentBegin.bind(this),
'agent end': this.handleAgentEnd.bind(this),
'connection': this.handleConnection.bind(this),
'note over': this.handleNote.bind(this),
'note left': this.handleNote.bind(this),
'note right': this.handleNote.bind(this),
'note between': this.handleNote.bind(this),
'block begin': this.handleBlockBegin.bind(this),
'block split': this.handleBlockSplit.bind(this),
'block end': this.handleBlockEnd.bind(this),
@ -44,14 +62,14 @@ define(['core/ArrayUtilities'], (array) => {
}
addBounds(target, agentL, agentR, involvedAgents = null) {
array.remove(target, agentL);
array.remove(target, agentR);
array.remove(target, agentL, agentEqCheck);
array.remove(target, agentR, agentEqCheck);
let indexL = 0;
let indexR = target.length;
if(involvedAgents) {
const found = (involvedAgents
.map((agent) => target.indexOf(agent))
.map((agent) => array.indexOf(target, agent, agentEqCheck))
.filter((p) => (p !== -1))
);
indexL = found.reduce((a, b) => Math.min(a, b), target.length);
@ -62,9 +80,21 @@ define(['core/ArrayUtilities'], (array) => {
target.splice(indexR + 1, 0, agentR);
}
addStage(stage, isVisible = true) {
this.currentSection.stages.push(stage);
if(isVisible) {
this.currentNest.hasContent = true;
}
}
defineAgents(agents) {
array.mergeSets(this.currentNest.agents, agents, agentEqCheck);
array.mergeSets(this.agents, agents, agentEqCheck);
}
setAgentVis(agents, visible, mode, checked = false) {
const filteredAgents = agents.filter((agent) => {
const state = this.agentStates.get(agent) || DEFAULT_AGENT;
const state = this.agentStates.get(agent.name) || DEFAULT_AGENT;
if(state.locked) {
if(checked) {
throw new Error('Cannot begin/end agent: ' + agent);
@ -78,33 +108,32 @@ define(['core/ArrayUtilities'], (array) => {
return;
}
filteredAgents.forEach((agent) => {
const state = this.agentStates.get(agent);
const state = this.agentStates.get(agent.name);
if(state) {
state.visible = visible;
} else {
this.agentStates.set(agent, new AgentState(visible));
this.agentStates.set(agent.name, new AgentState(visible));
}
});
const type = (visible ? 'agent begin' : 'agent end');
const existing = array.last(this.currentSection.stages) || {};
const agentNames = filteredAgents.map(getAgentName);
if(existing.type === type && existing.mode === mode) {
array.mergeSets(existing.agents, filteredAgents);
array.mergeSets(existing.agentNames, agentNames);
} else {
this.currentSection.stages.push({
this.addStage({
type,
agents: filteredAgents,
agentNames,
mode,
});
this.currentNest.hasContent = true;
}
array.mergeSets(this.currentNest.agents, filteredAgents);
array.mergeSets(this.agents, filteredAgents);
this.defineAgents(filteredAgents);
}
beginNested(mode, label, name) {
const nameL = name + '[';
const nameR = name + ']';
const agents = [nameL, nameR];
const leftAgent = makeAgent(name + '[', {anchorRight: true});
const rightAgent = makeAgent(name + ']');
const agents = [leftAgent, rightAgent];
const stages = [];
this.currentSection = {
mode,
@ -113,57 +142,80 @@ define(['core/ArrayUtilities'], (array) => {
};
this.currentNest = {
agents,
leftAgent,
rightAgent,
hasContent: false,
stage: {
type: 'block',
sections: [this.currentSection],
left: nameL,
right: nameR,
left: leftAgent.name,
right: rightAgent.name,
},
};
this.agentStates.set(nameL, LOCKED_AGENT);
this.agentStates.set(nameR, LOCKED_AGENT);
this.agentStates.set(leftAgent.name, LOCKED_AGENT);
this.agentStates.set(rightAgent.name, LOCKED_AGENT);
this.nesting.push(this.currentNest);
return {agents, stages};
}
handleMark(stage) {
this.markers.add(stage.name);
this.currentSection.stages.push(stage);
handleMark({name}) {
this.markers.add(name);
this.addStage({type: 'mark', name}, false);
}
handleAsync(stage) {
if(stage.target !== '' && !this.markers.has(stage.target)) {
throw new Error('Unknown marker: ' + stage.target);
handleAsync({target}) {
if(target !== '' && !this.markers.has(target)) {
throw new Error('Unknown marker: ' + target);
}
this.currentSection.stages.push(stage);
this.addStage({type: 'async', target}, false);
}
handleNote(stage) {
if(stage.agents.length === 0) {
this.handleUnknownStage(Object.assign({}, stage, {
agents: NOTE_DEFAULT_AGENTS[stage.type] || [],
}));
handleConnection({agents, label, line, left, right}) {
const colAgents = agents.map(convertAgent);
this.setAgentVis(colAgents, true, 'box');
this.defineAgents(colAgents);
this.addStage({
type: 'connection',
agentNames: agents.map(getAgentName),
label,
line,
left,
right,
});
}
handleNote({type, agents, mode, label}) {
let colAgents = null;
if(agents.length === 0) {
colAgents = NOTE_DEFAULT_AGENTS[type] || [];
} else {
this.handleUnknownStage(stage);
colAgents = agents.map(convertAgent);
}
this.setAgentVis(colAgents, true, 'box');
this.defineAgents(colAgents);
this.addStage({
type,
agentNames: colAgents.map(getAgentName),
mode,
label,
});
}
handleAgentDefine({agents}) {
const agentNames = agents.map((agent) => agent.name);
array.mergeSets(this.currentNest.agents, agentNames);
array.mergeSets(this.agents, agentNames);
const colAgents = agents.map(convertAgent);
this.defineAgents(colAgents);
}
handleAgentBegin({agents, mode}) {
const agentNames = agents.map((agent) => agent.name);
this.setAgentVis(agentNames, true, mode, true);
this.setAgentVis(agents.map(convertAgent), true, mode, true);
}
handleAgentEnd({agents, mode}) {
const agentNames = agents.map((agent) => agent.name);
this.setAgentVis(agentNames, false, mode, true);
this.setAgentVis(agents.map(convertAgent), false, mode, true);
}
handleBlockBegin({mode, label}) {
@ -192,45 +244,23 @@ define(['core/ArrayUtilities'], (array) => {
if(this.nesting.length <= 1) {
throw new Error('Invalid block nesting (too many "end"s)');
}
const {hasContent, stage, agents} = this.nesting.pop();
const nested = this.nesting.pop();
this.currentNest = array.last(this.nesting);
this.currentSection = array.last(this.currentNest.stage.sections);
if(hasContent) {
array.mergeSets(this.currentNest.agents, agents);
array.mergeSets(this.agents, agents);
if(nested.hasContent) {
this.defineAgents(nested.agents);
this.addBounds(
this.agents,
stage.left,
stage.right,
agents
nested.leftAgent,
nested.rightAgent,
nested.agents
);
this.currentSection.stages.push(stage);
this.currentNest.hasContent = true;
this.addStage(nested.stage);
}
}
handleUnknownStage(stage) {
if(stage.agents) {
const agentNames = stage.agents.map((agent) => agent.name);
this.setAgentVis(agentNames, true, 'box');
array.mergeSets(this.currentNest.agents, agentNames);
array.mergeSets(this.agents, agentNames);
this.currentSection.stages.push(Object.assign({}, stage, {
agents: agentNames,
}));
} else {
this.currentSection.stages.push(stage);
}
this.currentNest.hasContent = true;
}
handleStage(stage) {
const handler = this.stageHandlers[stage.type];
if(handler) {
handler(stage);
} else {
this.handleUnknownStage(stage);
}
this.stageHandlers[stage.type](stage);
}
generate({stages, meta = {}}) {
@ -254,8 +284,8 @@ define(['core/ArrayUtilities'], (array) => {
this.addBounds(
this.agents,
this.currentNest.stage.left,
this.currentNest.stage.right
this.currentNest.leftAgent,
this.currentNest.rightAgent
);
return {

View File

@ -3,13 +3,122 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
const generator = new Generator();
const AGENT_DEFINE = 'agent define';
const AGENT_BEGIN = 'agent begin';
const AGENT_END = 'agent end';
const parsed = {
blockBegin: (mode, label) => {
return {type: 'block begin', mode, label};
},
const BLOCK_BEGIN = 'block begin';
const BLOCK_SPLIT = 'block split';
const BLOCK_END = 'block end';
blockSplit: (mode, label) => {
return {type: 'block split', mode, label};
},
blockEnd: () => {
return {type: 'block end'};
},
defineAgents: (agentNames) => {
return {
type: 'agent define',
agents: agentNames.map((name) => ({name})),
};
},
beginAgents: (agentNames, {mode = 'box'} = {}) => {
return {
type: 'agent begin',
agents: agentNames.map((name) => ({name})),
mode,
};
},
endAgents: (agentNames, {mode = 'cross'} = {}) => {
return {
type: 'agent end',
agents: agentNames.map((name) => ({name})),
mode,
};
},
connect: (agentNames, {
label = '',
line = '',
left = false,
right = false,
} = {}) => {
return {
type: 'connection',
agents: agentNames.map((name) => ({name})),
label,
line,
left,
right,
};
},
note: (agentNames, {
type = 'note over',
mode = '',
label = '',
} = {}) => {
return {
type,
agents: agentNames.map((name) => ({name})),
mode,
label,
};
},
};
const generated = {
beginAgents: (agentNames, {
mode = jasmine.anything(),
} = {}) => {
return {
type: 'agent begin',
agentNames,
mode,
};
},
endAgents: (agentNames, {
mode = jasmine.anything(),
} = {}) => {
return {
type: 'agent end',
agentNames,
mode,
};
},
connect: (agentNames, {
label = jasmine.anything(),
line = jasmine.anything(),
left = jasmine.anything(),
right = jasmine.anything(),
} = {}) => {
return {
type: 'connection',
agentNames,
label,
line,
left,
right,
};
},
note: (agentNames, {
type = jasmine.anything(),
mode = jasmine.anything(),
label = jasmine.anything(),
} = {}) => {
return {
type,
agentNames,
mode,
label,
};
},
};
describe('.generate', () => {
it('propagates title metadata', () => {
@ -28,7 +137,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('includes implicit hidden left/right agents', () => {
const sequence = generator.generate({stages: []});
expect(sequence.agents).toEqual(['[', ']']);
expect(sequence.agents).toEqual([
{name: '[', anchorRight: true},
{name: ']', anchorRight: false},
]);
});
it('passes marks and async through', () => {
@ -53,39 +165,54 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('returns aggregated agents', () => {
const sequence = generator.generate({stages: [
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: '<-', agents: [{name: 'C'}, {name: 'D'}]},
{type: AGENT_BEGIN, agents: [{name: 'E'}], mode: 'box'},
parsed.connect(['A', 'B']),
parsed.connect(['C', 'D']),
parsed.beginAgents(['E']),
]});
expect(sequence.agents).toEqual(
['[', 'A', 'B', 'C', 'D', 'E', ']']
);
expect(sequence.agents).toEqual([
{name: '[', anchorRight: true},
{name: 'A', anchorRight: false},
{name: 'B', anchorRight: false},
{name: 'C', anchorRight: false},
{name: 'D', anchorRight: false},
{name: 'E', anchorRight: false},
{name: ']', anchorRight: false},
]);
});
it('always puts the implicit right agent on the right', () => {
const sequence = generator.generate({stages: [
{type: '->', agents: [{name: ']'}, {name: 'B'}]},
parsed.connect([']', 'B']),
]});
expect(sequence.agents).toEqual(['[', 'B', ']']);
expect(sequence.agents).toEqual([
{name: '[', anchorRight: true},
{name: 'B', anchorRight: false},
{name: ']', anchorRight: false},
]);
});
it('accounts for define calls when ordering agents', () => {
const sequence = generator.generate({stages: [
{type: AGENT_DEFINE, agents: [{name: 'B'}]},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
parsed.defineAgents(['B']),
parsed.connect(['A', 'B']),
]});
expect(sequence.agents).toEqual(['[', 'B', 'A', ']']);
expect(sequence.agents).toEqual([
{name: '[', anchorRight: true},
{name: 'B', anchorRight: false},
{name: 'A', anchorRight: false},
{name: ']', anchorRight: false},
]);
});
it('creates implicit begin stages for agents when used', () => {
const sequence = generator.generate({stages: [
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: '->', agents: [{name: 'B'}, {name: 'C'}]},
parsed.connect(['A', 'B']),
parsed.connect(['B', 'C']),
]});
expect(sequence.stages).toEqual([
{type: AGENT_BEGIN, agents: ['A', 'B'], mode: 'box'},
generated.beginAgents(['A', 'B']),
jasmine.anything(),
{type: AGENT_BEGIN, agents: ['C'], mode: 'box'},
generated.beginAgents(['C']),
jasmine.anything(),
jasmine.anything(),
]);
@ -93,11 +220,32 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('passes connections through', () => {
const sequence = generator.generate({stages: [
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
parsed.connect(['A', 'B']),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
{type: '->', agents: ['A', 'B']},
generated.connect(['A', 'B']),
jasmine.anything(),
]);
});
it('propagates connection information', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B'], {
label: 'foo',
line: 'bar',
left: true,
right: false,
}),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
generated.connect(['A', 'B'], {
label: 'foo',
line: 'bar',
left: true,
right: false,
}),
jasmine.anything(),
]);
});
@ -108,189 +256,182 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
terminators: 'foo',
},
stages: [
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
parsed.connect(['A', 'B']),
],
});
expect(sequence.stages).toEqual([
jasmine.anything(),
jasmine.anything(),
{type: AGENT_END, agents: ['A', 'B'], mode: 'foo'},
generated.endAgents(['A', 'B'], {mode: 'foo'}),
]);
});
it('defaults to mode "none" for implicit end stages', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B']),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
jasmine.anything(),
generated.endAgents(['A', 'B'], {mode: 'none'}),
]);
});
it('defaults to mode "cross" for explicit end stages', () => {
const sequence = generator.generate({stages: [
parsed.connect(['A', 'B']),
parsed.endAgents(['A', 'B']),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
jasmine.anything(),
generated.endAgents(['A', 'B'], {mode: 'cross'}),
]);
});
it('does not create duplicate begin stages', () => {
const sequence = generator.generate({stages: [
{type: AGENT_BEGIN, agents: [
{name: 'A'},
{name: 'B'},
{name: 'C'},
], mode: 'box'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: '->', agents: [{name: 'B'}, {name: 'C'}]},
parsed.beginAgents(['A', 'B', 'C']),
parsed.connect(['A', 'B']),
parsed.connect(['B', 'C']),
]});
expect(sequence.stages).toEqual([
{type: AGENT_BEGIN, agents: ['A', 'B', 'C'], mode: 'box'},
{type: '->', agents: jasmine.anything()},
{type: '->', agents: jasmine.anything()},
{type: AGENT_END, agents: ['A', 'B', 'C'], mode: 'none'},
generated.beginAgents(['A', 'B', 'C']),
generated.connect(jasmine.anything()),
generated.connect(jasmine.anything()),
generated.endAgents(['A', 'B', 'C']),
]);
});
it('redisplays agents if they have been hidden', () => {
const sequence = generator.generate({stages: [
{type: AGENT_BEGIN, agents: [
{name: 'A'},
{name: 'B'},
], mode: 'box'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: AGENT_END, agents: [{name: 'B'}], mode: 'cross'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
parsed.beginAgents(['A', 'B']),
parsed.connect(['A', 'B']),
parsed.endAgents(['B']),
parsed.connect(['A', 'B']),
]});
expect(sequence.stages).toEqual([
{type: AGENT_BEGIN, agents: ['A', 'B'], mode: 'box'},
jasmine.anything(),
{type: AGENT_END, agents: ['B'], mode: 'cross'},
{type: AGENT_BEGIN, agents: ['B'], mode: 'box'},
jasmine.anything(),
{type: AGENT_END, agents: ['A', 'B'], mode: 'none'},
generated.endAgents(['B']),
generated.beginAgents(['B']),
jasmine.anything(),
jasmine.anything(),
]);
});
it('collapses adjacent begin statements', () => {
const sequence = generator.generate({stages: [
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: AGENT_BEGIN, agents: [{name: 'D'}], mode: 'box'},
{type: '->', agents: [{name: 'B'}, {name: 'C'}]},
{type: '->', agents: [{name: 'C'}, {name: 'D'}]},
parsed.connect(['A', 'B']),
parsed.beginAgents(['D']),
parsed.connect(['B', 'C']),
parsed.connect(['C', 'D']),
]});
expect(sequence.stages).toEqual([
{type: AGENT_BEGIN, agents: ['A', 'B'], mode: 'box'},
{type: '->', agents: jasmine.anything()},
{type: AGENT_BEGIN, agents: ['D', 'C'], mode: 'box'},
{type: '->', agents: jasmine.anything()},
{type: '->', agents: jasmine.anything()},
generated.beginAgents(['A', 'B']),
generated.connect(jasmine.anything()),
generated.beginAgents(['D', 'C']),
generated.connect(jasmine.anything()),
generated.connect(jasmine.anything()),
jasmine.anything(),
]);
});
it('removes superfluous begin statements', () => {
const sequence = generator.generate({stages: [
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: AGENT_BEGIN, agents: [
{name: 'A'},
{name: 'C'},
{name: 'D'},
], mode: 'box'},
{type: AGENT_BEGIN, agents: [
{name: 'C'},
{name: 'E'},
], mode: 'box'},
parsed.connect(['A', 'B']),
parsed.beginAgents(['A', 'C', 'D']),
parsed.beginAgents(['C', 'E']),
]});
expect(sequence.stages).toEqual([
{type: AGENT_BEGIN, agents: ['A', 'B'], mode: 'box'},
{type: '->', agents: jasmine.anything()},
{type: AGENT_BEGIN, agents: ['C', 'D', 'E'], mode: 'box'},
generated.beginAgents(['A', 'B']),
generated.connect(jasmine.anything()),
generated.beginAgents(['C', 'D', 'E']),
jasmine.anything(),
]);
});
it('removes superfluous end statements', () => {
const sequence = generator.generate({stages: [
{type: AGENT_DEFINE, agents: [{name: 'E'}]},
{type: AGENT_BEGIN, agents: [
{name: 'C'},
{name: 'D'},
], mode: 'box'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: AGENT_END, agents: [
{name: 'A'},
{name: 'B'},
{name: 'C'},
], mode: 'cross'},
{type: AGENT_END, agents: [
{name: 'A'},
{name: 'D'},
{name: 'E'},
], mode: 'cross'},
parsed.defineAgents(['E']),
parsed.beginAgents(['C', 'D']),
parsed.connect(['A', 'B']),
parsed.endAgents(['A', 'B', 'C']),
parsed.endAgents(['A', 'D', 'E']),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
{type: '->', agents: jasmine.anything()},
{type: AGENT_END, agents: ['A', 'B', 'C', 'D'], mode: 'cross'},
generated.connect(jasmine.anything()),
generated.endAgents(['A', 'B', 'C', 'D']),
]);
});
it('does not merge different modes of end', () => {
const sequence = generator.generate({stages: [
{type: AGENT_BEGIN, agents: [
{name: 'C'},
{name: 'D'},
], mode: 'box'},
{type: '->', agents: [
{name: 'A'},
{name: 'B'},
]},
{type: AGENT_END, agents: [
{name: 'A'},
{name: 'B'},
{name: 'C'},
], mode: 'cross'},
parsed.beginAgents(['C', 'D']),
parsed.connect(['A', 'B']),
parsed.endAgents(['A', 'B', 'C']),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
{type: '->', agents: jasmine.anything()},
{type: AGENT_END, agents: ['A', 'B', 'C'], mode: 'cross'},
{type: AGENT_END, agents: ['D'], mode: 'none'},
generated.connect(jasmine.anything()),
generated.endAgents(['A', 'B', 'C'], {mode: 'cross'}),
generated.endAgents(['D'], {mode: 'none'}),
]);
});
it('creates virtual agents for block statements', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_END},
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
]});
expect(sequence.agents).toEqual(
['[', '__BLOCK0[', 'A', 'B', '__BLOCK0]', ']']
);
expect(sequence.agents).toEqual([
{name: '[', anchorRight: true},
{name: '__BLOCK0[', anchorRight: true},
{name: 'A', anchorRight: false},
{name: 'B', anchorRight: false},
{name: '__BLOCK0]', anchorRight: false},
{name: ']', anchorRight: false},
]);
});
it('positions virtual block agents near involved agents', () => {
const sequence = generator.generate({stages: [
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: [{name: 'C'}, {name: 'D'}]},
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: [{name: 'E'}, {name: 'F'}]},
{type: BLOCK_END},
{type: BLOCK_END},
{type: '->', agents: [{name: 'G'}, {name: 'H'}]},
parsed.connect(['A', 'B']),
parsed.blockBegin('if', 'abc'),
parsed.connect(['C', 'D']),
parsed.blockBegin('if', 'abc'),
parsed.connect(['E', 'F']),
parsed.blockEnd(),
parsed.blockEnd(),
parsed.connect(['G', 'H']),
]});
expect(sequence.agents).toEqual([
'[',
'A',
'B',
'__BLOCK0[',
'C',
'D',
'__BLOCK1[',
'E',
'F',
'__BLOCK1]',
'__BLOCK0]',
'G',
'H',
']',
{name: '[', anchorRight: true},
{name: 'A', anchorRight: false},
{name: 'B', anchorRight: false},
{name: '__BLOCK0[', anchorRight: true},
{name: 'C', anchorRight: false},
{name: 'D', anchorRight: false},
{name: '__BLOCK1[', anchorRight: true},
{name: 'E', anchorRight: false},
{name: 'F', anchorRight: false},
{name: '__BLOCK1]', anchorRight: false},
{name: '__BLOCK0]', anchorRight: false},
{name: 'G', anchorRight: false},
{name: 'H', anchorRight: false},
{name: ']', anchorRight: false},
]);
});
it('records virtual block agent names in blocks', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_END},
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
]});
const block0 = sequence.stages[0];
@ -301,47 +442,47 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('records all sections within blocks', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_SPLIT, mode: 'else', label: 'xyz'},
{type: '->', agents: [{name: 'A'}, {name: 'C'}]},
{type: BLOCK_END},
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockSplit('else', 'xyz'),
parsed.connect(['A', 'C']),
parsed.blockEnd(),
]});
const block0 = sequence.stages[0];
expect(block0.sections).toEqual([
{mode: 'if', label: 'abc', stages: [
{type: AGENT_BEGIN, agents: ['A', 'B'], mode: 'box'},
{type: '->', agents: ['A', 'B']},
generated.beginAgents(['A', 'B']),
generated.connect(['A', 'B']),
]},
{mode: 'else', label: 'xyz', stages: [
{type: AGENT_BEGIN, agents: ['C'], mode: 'box'},
{type: '->', agents: ['A', 'C']},
generated.beginAgents(['C']),
generated.connect(['A', 'C']),
]},
]);
});
it('records virtual block agents in nested blocks', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_SPLIT, mode: 'else', label: 'xyz'},
{type: BLOCK_BEGIN, mode: 'if', label: 'def'},
{type: '->', agents: [{name: 'A'}, {name: 'C'}]},
{type: BLOCK_END},
{type: BLOCK_END},
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockSplit('else', 'xyz'),
parsed.blockBegin('if', 'def'),
parsed.connect(['A', 'C']),
parsed.blockEnd(),
parsed.blockEnd(),
]});
expect(sequence.agents).toEqual([
'[',
'__BLOCK0[',
'__BLOCK1[',
'A',
'B',
'C',
'__BLOCK1]',
'__BLOCK0]',
']',
{name: '[', anchorRight: true},
{name: '__BLOCK0[', anchorRight: true},
{name: '__BLOCK1[', anchorRight: true},
{name: 'A', anchorRight: false},
{name: 'B', anchorRight: false},
{name: 'C', anchorRight: false},
{name: '__BLOCK1]', anchorRight: false},
{name: '__BLOCK0]', anchorRight: false},
{name: ']', anchorRight: false},
]);
const block0 = sequence.stages[0];
expect(block0.type).toEqual('block');
@ -356,23 +497,23 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('preserves block boundaries when agents exist outside', () => {
const sequence = generator.generate({stages: [
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: BLOCK_BEGIN, mode: 'if', label: 'def'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_END},
{type: BLOCK_END},
parsed.connect(['A', 'B']),
parsed.blockBegin('if', 'abc'),
parsed.blockBegin('if', 'def'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
parsed.blockEnd(),
]});
expect(sequence.agents).toEqual([
'[',
'__BLOCK0[',
'__BLOCK1[',
'A',
'B',
'__BLOCK1]',
'__BLOCK0]',
']',
{name: '[', anchorRight: true},
{name: '__BLOCK0[', anchorRight: true},
{name: '__BLOCK1[', anchorRight: true},
{name: 'A', anchorRight: false},
{name: 'B', anchorRight: false},
{name: '__BLOCK1]', anchorRight: false},
{name: '__BLOCK0]', anchorRight: false},
{name: ']', anchorRight: false},
]);
const block0 = sequence.stages[2];
expect(block0.type).toEqual('block');
@ -387,10 +528,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('allows empty block parts after split', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_SPLIT, mode: 'else', label: 'xyz'},
{type: BLOCK_END},
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockSplit('else', 'xyz'),
parsed.blockEnd(),
]});
const block0 = sequence.stages[0];
@ -405,10 +546,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('allows empty block parts before split', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: BLOCK_SPLIT, mode: 'else', label: 'xyz'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_END},
parsed.blockBegin('if', 'abc'),
parsed.blockSplit('else', 'xyz'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
]});
const block0 = sequence.stages[0];
@ -423,11 +564,11 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('removes entirely empty blocks', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: BLOCK_SPLIT, mode: 'else', label: 'xyz'},
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: BLOCK_END},
{type: BLOCK_END},
parsed.blockBegin('if', 'abc'),
parsed.blockSplit('else', 'xyz'),
parsed.blockBegin('if', 'abc'),
parsed.blockEnd(),
parsed.blockEnd(),
]});
expect(sequence.stages).toEqual([]);
@ -435,10 +576,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('removes blocks containing only define statements / markers', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: AGENT_DEFINE, agents: [{name: 'A'}]},
parsed.blockBegin('if', 'abc'),
parsed.defineAgents(['A']),
{type: 'mark', name: 'foo'},
{type: BLOCK_END},
parsed.blockEnd(),
]});
expect(sequence.stages).toEqual([]);
@ -446,24 +587,27 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('does not create virtual agents for empty blocks', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: BLOCK_SPLIT, mode: 'else', label: 'xyz'},
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: BLOCK_END},
{type: BLOCK_END},
parsed.blockBegin('if', 'abc'),
parsed.blockSplit('else', 'xyz'),
parsed.blockBegin('if', 'abc'),
parsed.blockEnd(),
parsed.blockEnd(),
]});
expect(sequence.agents).toEqual(['[', ']']);
expect(sequence.agents).toEqual([
{name: '[', anchorRight: true},
{name: ']', anchorRight: false},
]);
});
it('removes entirely empty nested blocks', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_SPLIT, mode: 'else', label: 'xyz'},
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: BLOCK_END},
{type: BLOCK_END},
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockSplit('else', 'xyz'),
parsed.blockBegin('if', 'abc'),
parsed.blockEnd(),
parsed.blockEnd(),
]});
const block0 = sequence.stages[0];
@ -478,83 +622,100 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('rejects unterminated blocks', () => {
expect(() => generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
]})).toThrow();
expect(() => generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: BLOCK_BEGIN, mode: 'if', label: 'def'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_END},
parsed.blockBegin('if', 'abc'),
parsed.blockBegin('if', 'def'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
]})).toThrow();
});
it('rejects extra block terminations', () => {
expect(() => generator.generate({stages: [
{type: BLOCK_END},
parsed.blockEnd(),
]})).toThrow();
expect(() => generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_END},
{type: BLOCK_END},
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
parsed.blockEnd(),
]})).toThrow();
});
it('rejects block splitting without a block', () => {
expect(() => generator.generate({stages: [
{type: BLOCK_SPLIT, mode: 'else', label: 'xyz'},
parsed.blockSplit('else', 'xyz'),
]})).toThrow();
expect(() => generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_END},
{type: BLOCK_SPLIT, mode: 'else', label: 'xyz'},
parsed.blockBegin('if', 'abc'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
parsed.blockSplit('else', 'xyz'),
]})).toThrow();
});
it('rejects block splitting in non-splittable blocks', () => {
expect(() => generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'repeat', label: 'abc'},
{type: BLOCK_SPLIT, mode: 'else', label: 'xyz'},
{type: '->', agents: [{name: 'A'}, {name: 'B'}]},
{type: BLOCK_END},
parsed.blockBegin('repeat', 'abc'),
parsed.blockSplit('else', 'xyz'),
parsed.connect(['A', 'B']),
parsed.blockEnd(),
]})).toThrow();
});
it('passes notes through', () => {
const sequence = generator.generate({stages: [
parsed.note(['A', 'B'], {
type: 'note right',
mode: 'foo',
label: 'bar',
}),
]});
expect(sequence.stages).toEqual([
jasmine.anything(),
generated.note(['A', 'B'], {
type: 'note right',
mode: 'foo',
label: 'bar',
}),
jasmine.anything(),
]);
});
it('defaults to showing notes around the entire diagram', () => {
const sequence = generator.generate({stages: [
{type: 'note right', agents: [], foo: 'bar'},
{type: 'note left', agents: [], foo: 'bar'},
{type: 'note over', agents: [], foo: 'bar'},
{type: 'note right', agents: [{name: '['}], foo: 'bar'},
parsed.note([], {type: 'note right'}),
parsed.note([], {type: 'note left'}),
parsed.note([], {type: 'note over'}),
]});
expect(sequence.stages).toEqual([
{type: 'note right', agents: [']'], foo: 'bar'},
{type: 'note left', agents: ['['], foo: 'bar'},
{type: 'note over', agents: ['[', ']'], foo: 'bar'},
{type: 'note right', agents: ['['], foo: 'bar'},
generated.note([']'], {type: 'note right'}),
generated.note(['['], {type: 'note left'}),
generated.note(['[', ']'], {type: 'note over'}),
]);
});
it('rejects attempts to change implicit agents', () => {
expect(() => generator.generate({stages: [
{type: AGENT_BEGIN, agents: [{name: '['}], mode: 'box'},
parsed.beginAgents(['[']),
]})).toThrow();
expect(() => generator.generate({stages: [
{type: AGENT_BEGIN, agents: [{name: ']'}], mode: 'box'},
parsed.beginAgents([']']),
]})).toThrow();
expect(() => generator.generate({stages: [
{type: AGENT_END, agents: [{name: '['}], mode: 'cross'},
parsed.endAgents(['[']),
]})).toThrow();
expect(() => generator.generate({stages: [
{type: AGENT_END, agents: [{name: ']'}], mode: 'cross'},
parsed.endAgents([']']),
]})).toThrow();
});
});

View File

@ -140,11 +140,11 @@ define([
this.sizer = new this.SVGTextBlockClass.SizeTester(this.base);
}
findExtremes(agents) {
findExtremes(agentNames) {
let min = null;
let max = null;
agents.forEach((agent) => {
const info = this.agentInfos.get(agent);
agentNames.forEach((name) => {
const info = this.agentInfos.get(name);
if(min === null || info.index < min.index) {
min = info;
}
@ -158,32 +158,32 @@ define([
};
}
addSeparation(agent1, agent2, dist) {
const info1 = this.agentInfos.get(agent1);
const info2 = this.agentInfos.get(agent2);
addSeparation(agentName1, agentName2, dist) {
const info1 = this.agentInfos.get(agentName1);
const info2 = this.agentInfos.get(agentName2);
const d1 = info1.separations.get(agent2) || 0;
info1.separations.set(agent2, Math.max(d1, dist));
const d1 = info1.separations.get(agentName2) || 0;
info1.separations.set(agentName2, Math.max(d1, dist));
const d2 = info2.separations.get(agent1) || 0;
info2.separations.set(agent1, Math.max(d2, dist));
const d2 = info2.separations.get(agentName1) || 0;
info2.separations.set(agentName1, Math.max(d2, dist));
}
addSeparations(agents, agentSpaces) {
agents.forEach((agentR) => {
const infoR = this.agentInfos.get(agentR);
const sepR = agentSpaces.get(agentR) || SEP_ZERO;
addSeparations(agentNames, agentSpaces) {
agentNames.forEach((agentNameR) => {
const infoR = this.agentInfos.get(agentNameR);
const sepR = agentSpaces.get(agentNameR) || SEP_ZERO;
infoR.maxRPad = Math.max(infoR.maxRPad, sepR.right);
infoR.maxLPad = Math.max(infoR.maxLPad, sepR.left);
agents.forEach((agentL) => {
const infoL = this.agentInfos.get(agentL);
agentNames.forEach((agentNameL) => {
const infoL = this.agentInfos.get(agentNameL);
if(infoL.index >= infoR.index) {
return;
}
const sepL = agentSpaces.get(agentL) || SEP_ZERO;
const sepL = agentSpaces.get(agentNameL) || SEP_ZERO;
this.addSeparation(
agentR,
agentL,
agentNameR,
agentNameL,
sepR.left + sepL.right + this.theme.agentMargin
);
});
@ -236,25 +236,25 @@ define([
return {left: 0, right: 0};
}
separationAgent({type, mode, agents}) {
separationAgent({type, mode, agentNames}) {
if(type === 'agent begin') {
array.mergeSets(this.visibleAgents, agents);
array.mergeSets(this.visibleAgents, agentNames);
}
const agentSpaces = new Map();
agents.forEach((agent) => {
const info = this.agentInfos.get(agent);
agentNames.forEach((name) => {
const info = this.agentInfos.get(name);
const separationFn = this.separationAgentCap[mode];
agentSpaces.set(agent, separationFn(info));
agentSpaces.set(name, separationFn(info));
});
this.addSeparations(this.visibleAgents, agentSpaces);
if(type === 'agent end') {
array.removeAll(this.visibleAgents, agents);
array.removeAll(this.visibleAgents, agentNames);
}
}
separationConnection({agents, label}) {
separationConnection({agentNames, label}) {
const config = this.theme.connect;
const labelWidth = (
@ -263,9 +263,9 @@ define([
);
const strokeWidth = this.theme.agentLineAttrs['stroke-width'];
if(agents[0] === agents[1]) {
if(agentNames[0] === agentNames[1]) {
const agentSpaces = new Map();
agentSpaces.set(agents[0], {
agentSpaces.set(agentNames[0], {
left: 0,
right: (
labelWidth +
@ -277,14 +277,14 @@ define([
this.addSeparations(this.visibleAgents, agentSpaces);
} else {
this.addSeparation(
agents[0],
agents[1],
agentNames[0],
agentNames[1],
labelWidth + config.arrow.width * 2 + strokeWidth
);
}
}
separationNoteOver({agents, mode, label}) {
separationNoteOver({agentNames, mode, label}) {
const config = this.theme.note[mode];
const width = (
this.sizer.measure(config.labelAttrs, label).width +
@ -293,8 +293,8 @@ define([
);
const agentSpaces = new Map();
if(agents.length > 1) {
const {left, right} = this.findExtremes(agents);
if(agentNames.length > 1) {
const {left, right} = this.findExtremes(agentNames);
this.addSeparation(
left,
@ -308,7 +308,7 @@ define([
agentSpaces.set(left, {left: config.overlap.left, right: 0});
agentSpaces.set(right, {left: 0, right: config.overlap.right});
} else {
agentSpaces.set(agents[0], {
agentSpaces.set(agentNames[0], {
left: width / 2,
right: width / 2,
});
@ -316,9 +316,9 @@ define([
this.addSeparations(this.visibleAgents, agentSpaces);
}
separationNoteSide(isRight, {agents, mode, label}) {
separationNoteSide(isRight, {agentNames, mode, label}) {
const config = this.theme.note[mode];
const {left, right} = this.findExtremes(agents);
const {left, right} = this.findExtremes(agentNames);
const width = (
this.sizer.measure(config.labelAttrs, label).width +
config.padding.left +
@ -336,9 +336,9 @@ define([
this.addSeparations(this.visibleAgents, agentSpaces);
}
separationNoteBetween({agents, mode, label}) {
separationNoteBetween({agentNames, mode, label}) {
const config = this.theme.note[mode];
const {left, right} = this.findExtremes(agents);
const {left, right} = this.findExtremes(agentNames);
this.addSeparation(
left,
@ -462,8 +462,8 @@ define([
};
}
checkAgentRange(agents) {
const {left, right} = this.findExtremes(agents);
checkAgentRange(agentNames) {
const {left, right} = this.findExtremes(agentNames);
const leftX = this.agentInfos.get(left).x;
const rightX = this.agentInfos.get(right).x;
this.agentInfos.forEach((agentInfo) => {
@ -473,8 +473,8 @@ define([
});
}
markAgentRange(agents) {
const {left, right} = this.findExtremes(agents);
markAgentRange(agentNames) {
const {left, right} = this.findExtremes(agentNames);
const leftX = this.agentInfos.get(left).x;
const rightX = this.agentInfos.get(right).x;
this.agentInfos.forEach((agentInfo) => {
@ -484,24 +484,24 @@ define([
});
}
renderAgentBegin({mode, agents}) {
this.checkAgentRange(agents);
renderAgentBegin({mode, agentNames}) {
this.checkAgentRange(agentNames);
let maxHeight = 0;
agents.forEach((agent) => {
const agentInfo = this.agentInfos.get(agent);
agentNames.forEach((name) => {
const agentInfo = this.agentInfos.get(name);
const shifts = this.renderAgentCap[mode](agentInfo);
maxHeight = Math.max(maxHeight, shifts.height);
agentInfo.latestYStart = this.currentY + shifts.lineBottom;
});
this.currentY += maxHeight + this.theme.actionMargin;
this.markAgentRange(agents);
this.markAgentRange(agentNames);
}
renderAgentEnd({mode, agents}) {
this.checkAgentRange(agents);
renderAgentEnd({mode, agentNames}) {
this.checkAgentRange(agentNames);
let maxHeight = 0;
agents.forEach((agent) => {
const agentInfo = this.agentInfos.get(agent);
agentNames.forEach((name) => {
const agentInfo = this.agentInfos.get(name);
const x = agentInfo.x;
const shifts = this.renderAgentCap[mode](agentInfo);
maxHeight = Math.max(maxHeight, shifts.height);
@ -515,12 +515,12 @@ define([
agentInfo.latestYStart = null;
});
this.currentY += maxHeight + this.theme.actionMargin;
this.markAgentRange(agents);
this.markAgentRange(agentNames);
}
renderSelfConnection({label, agents, line, left, right}) {
renderSelfConnection({label, agentNames, line, left, right}) {
const config = this.theme.connect;
const from = this.agentInfos.get(agents[0]);
const from = this.agentInfos.get(agentNames[0]);
const dy = config.arrow.height / 2;
const short = this.theme.agentLineAttrs['stroke-width'];
@ -591,10 +591,10 @@ define([
this.currentY = y1 + dy + this.theme.actionMargin;
}
renderSimpleConnection({label, agents, line, left, right}) {
renderSimpleConnection({label, agentNames, line, left, right}) {
const config = this.theme.connect;
const from = this.agentInfos.get(agents[0]);
const to = this.agentInfos.get(agents[1]);
const from = this.agentInfos.get(agentNames[0]);
const to = this.agentInfos.get(agentNames[1]);
const dy = config.arrow.height / 2;
const dir = (from.x < to.x) ? 1 : -1;
@ -650,13 +650,13 @@ define([
}
renderConnection(stage) {
this.checkAgentRange(stage.agents);
if(stage.agents[0] === stage.agents[1]) {
this.checkAgentRange(stage.agentNames);
if(stage.agentNames[0] === stage.agentNames[1]) {
this.renderSelfConnection(stage);
} else {
this.renderSimpleConnection(stage);
}
this.markAgentRange(stage.agents);
this.markAgentRange(stage.agentNames);
}
renderNote({xMid = null, x0 = null, x1 = null}, anchor, mode, label) {
@ -721,53 +721,53 @@ define([
);
}
renderNoteOver({agents, mode, label}) {
this.checkAgentRange(agents);
renderNoteOver({agentNames, mode, label}) {
this.checkAgentRange(agentNames);
const config = this.theme.note[mode];
if(agents.length > 1) {
const {left, right} = this.findExtremes(agents);
if(agentNames.length > 1) {
const {left, right} = this.findExtremes(agentNames);
this.renderNote({
x0: this.agentInfos.get(left).x - config.overlap.left,
x1: this.agentInfos.get(right).x + config.overlap.right,
}, 'middle', mode, label);
} else {
const xMid = this.agentInfos.get(agents[0]).x;
const xMid = this.agentInfos.get(agentNames[0]).x;
this.renderNote({xMid}, 'middle', mode, label);
}
this.markAgentRange(agents);
this.markAgentRange(agentNames);
}
renderNoteLeft({agents, mode, label}) {
this.checkAgentRange(agents);
renderNoteLeft({agentNames, mode, label}) {
this.checkAgentRange(agentNames);
const config = this.theme.note[mode];
const {left} = this.findExtremes(agents);
const {left} = this.findExtremes(agentNames);
const x1 = this.agentInfos.get(left).x - config.margin.right;
this.renderNote({x1}, 'end', mode, label);
this.markAgentRange(agents);
this.markAgentRange(agentNames);
}
renderNoteRight({agents, mode, label}) {
this.checkAgentRange(agents);
renderNoteRight({agentNames, mode, label}) {
this.checkAgentRange(agentNames);
const config = this.theme.note[mode];
const {right} = this.findExtremes(agents);
const {right} = this.findExtremes(agentNames);
const x0 = this.agentInfos.get(right).x + config.margin.left;
this.renderNote({x0}, 'start', mode, label);
this.markAgentRange(agents);
this.markAgentRange(agentNames);
}
renderNoteBetween({agents, mode, label}) {
this.checkAgentRange(agents);
const {left, right} = this.findExtremes(agents);
renderNoteBetween({agentNames, mode, label}) {
this.checkAgentRange(agentNames);
const {left, right} = this.findExtremes(agentNames);
const xMid = (
this.agentInfos.get(left).x +
this.agentInfos.get(right).x
) / 2;
this.renderNote({xMid}, 'middle', mode, label);
this.markAgentRange(agents);
this.markAgentRange(agentNames);
}
renderBlockBegin(scope, {left, right}) {
@ -891,9 +891,9 @@ define([
buildAgentInfos(agents, stages) {
this.agentInfos = new Map();
agents.forEach((agent, index) => {
this.agentInfos.set(agent, {
label: agent,
anchorRight: agent.endsWith('['),
this.agentInfos.set(agent.name, {
label: agent.name,
anchorRight: agent.anchorRight,
index,
x: null,
latestYStart: null,

View File

@ -25,13 +25,13 @@ defineDescribe('Sequence Renderer', [
});
});
function connectionStage(agents, label = '') {
function connectionStage(agentNames, label = '') {
return {
type: 'connection',
line: 'solid',
left: false,
right: true,
agents,
agentNames,
label,
};
}
@ -40,7 +40,12 @@ defineDescribe('Sequence Renderer', [
it('populates the SVG with content', () => {
renderer.render({
meta: {title: 'Title'},
agents: ['[', 'Col 1', 'Col 2', ']'],
agents: [
{name: '[', anchorRight: true},
{name: 'Col 1', anchorRight: false},
{name: 'Col 2', anchorRight: false},
{name: ']', anchorRight: false},
],
stages: [],
});
const element = renderer.svg();
@ -55,11 +60,16 @@ defineDescribe('Sequence Renderer', [
renderer.render({
meta: {title: ''},
agents: ['[', 'A', 'B', ']'],
agents: [
{name: '[', anchorRight: true},
{name: 'A', anchorRight: false},
{name: 'B', anchorRight: false},
{name: ']', anchorRight: false},
],
stages: [
{type: 'agent begin', agents: ['A', 'B'], mode: 'box'},
{type: 'agent begin', agentNames: ['A', 'B'], mode: 'box'},
connectionStage(['A', 'B']),
{type: 'agent end', agents: ['A', 'B'], mode: 'none'},
{type: 'agent end', agentNames: ['A', 'B'], mode: 'none'},
],
});
@ -80,14 +90,28 @@ defineDescribe('Sequence Renderer', [
renderer.render({
meta: {title: ''},
agents: ['[', 'A', 'B', 'C', ']'],
agents: [
{name: '[', anchorRight: true},
{name: 'A', anchorRight: false},
{name: 'B', anchorRight: false},
{name: 'C', anchorRight: false},
{name: ']', anchorRight: false},
],
stages: [
{type: 'agent begin', agents: ['A', 'B', 'C'], mode: 'box'},
{
type: 'agent begin',
agentNames: ['A', 'B', 'C'],
mode: 'box',
},
connectionStage(['[', 'A']),
connectionStage(['A', 'B']),
connectionStage(['B', 'C']),
connectionStage(['C', ']']),
{type: 'agent end', agents: ['A', 'B', 'C'], mode: 'none'},
{
type: 'agent end',
agentNames: ['A', 'B', 'C'],
mode: 'none',
},
],
});
@ -115,17 +139,24 @@ defineDescribe('Sequence Renderer', [
renderer.render({
meta: {title: ''},
agents: ['[', 'A', 'B', 'C', 'D', ']'],
agents: [
{name: '[', anchorRight: true},
{name: 'A', anchorRight: false},
{name: 'B', anchorRight: false},
{name: 'C', anchorRight: false},
{name: 'D', anchorRight: false},
{name: ']', anchorRight: false},
],
stages: [
{type: 'agent begin', agents: ['A', 'B'], mode: 'box'},
{type: 'agent begin', agentNames: ['A', 'B'], mode: 'box'},
connectionStage(['A', 'B'], 'short'),
{type: 'agent end', agents: ['B'], mode: 'cross'},
{type: 'agent begin', agents: ['C'], mode: 'box'},
{type: 'agent end', agentNames: ['B'], mode: 'cross'},
{type: 'agent begin', agentNames: ['C'], mode: 'box'},
connectionStage(['A', 'C'], 'long description here'),
{type: 'agent end', agents: ['C'], mode: 'cross'},
{type: 'agent begin', agents: ['D'], mode: 'box'},
{type: 'agent end', agentNames: ['C'], mode: 'cross'},
{type: 'agent begin', agentNames: ['D'], mode: 'box'},
connectionStage(['A', 'D'], 'short again'),
{type: 'agent end', agents: ['A', 'D'], mode: 'cross'},
{type: 'agent end', agentNames: ['A', 'D'], mode: 'cross'},
],
});

View File

@ -1,5 +1,5 @@
/* jshint -W072 */
defineDescribe('Sequence Renderer', [
defineDescribe('Sequence Integration', [
'./Parser',
'./Generator',
'./Renderer',