diff --git a/scripts/core/ArrayUtilities.js b/scripts/core/ArrayUtilities.js index d8a3b67..1c3efc0 100644 --- a/scripts/core/ArrayUtilities.js +++ b/scripts/core/ArrayUtilities.js @@ -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, diff --git a/scripts/core/ArrayUtilities_spec.js b/scripts/core/ArrayUtilities_spec.js index cc61d3a..f12461f 100644 --- a/scripts/core/ArrayUtilities_spec.js +++ b/scripts/core/ArrayUtilities_spec.js @@ -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', () => { diff --git a/scripts/sequence/Generator.js b/scripts/sequence/Generator.js index 223b0bb..31bc9bd 100644 --- a/scripts/sequence/Generator.js +++ b/scripts/sequence/Generator.js @@ -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 { diff --git a/scripts/sequence/Generator_spec.js b/scripts/sequence/Generator_spec.js index 0d28204..f91f8a0 100644 --- a/scripts/sequence/Generator_spec.js +++ b/scripts/sequence/Generator_spec.js @@ -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(); }); }); diff --git a/scripts/sequence/Renderer.js b/scripts/sequence/Renderer.js index 8cf6d7b..2191769 100644 --- a/scripts/sequence/Renderer.js +++ b/scripts/sequence/Renderer.js @@ -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, diff --git a/scripts/sequence/Renderer_spec.js b/scripts/sequence/Renderer_spec.js index 2f7441b..b0215ba 100644 --- a/scripts/sequence/Renderer_spec.js +++ b/scripts/sequence/Renderer_spec.js @@ -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'}, ], }); diff --git a/scripts/sequence/sequence_integration_spec.js b/scripts/sequence/sequence_integration_spec.js index ebbbfcb..33286d1 100644 --- a/scripts/sequence/sequence_integration_spec.js +++ b/scripts/sequence/sequence_integration_spec.js @@ -1,5 +1,5 @@ /* jshint -W072 */ -defineDescribe('Sequence Renderer', [ +defineDescribe('Sequence Integration', [ './Parser', './Generator', './Renderer',