diff --git a/.gitignore b/.gitignore index 78e5cae..ba9f817 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .DS_Store xcuserdata xcshareddata +ephemeral diff --git a/SequenceDiagram.xcworkspace/contents.xcworkspacedata b/SequenceDiagram.xcworkspace/contents.xcworkspacedata index a8431d6..0ed3e16 100644 --- a/SequenceDiagram.xcworkspace/contents.xcworkspacedata +++ b/SequenceDiagram.xcworkspace/contents.xcworkspacedata @@ -7,18 +7,18 @@ + + + + - - - - diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index cf0e7d1..3bcfac2 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -40,10 +40,10 @@ web modules unless a flag is set (FireFox 60 will support them fully). The editor and library page do not require web modules, so should have wider support. -To run the linter, run the command: +To run the non-browser tests and linter, run the command: ```shell -npm run lint; +npm test; ``` And to rebuild the minified sources, run: @@ -52,7 +52,18 @@ And to rebuild the minified sources, run: npm run minify; ``` -Currently there are no command-line tests; only the browser tests. +## Commands + +The available commands are: + +* `npm start`: runs a webserver on + [localhost:8080](http://localhost:8080) +* `npm test`: runs the `unit-test` and `lint` commands +* `npm run unit-test`: runs non-browser-based unit tests in NodeJS +* `npm run lint`: runs the linter against all source and test files +* `npm run minify`: runs the `minify-lib` and `minify-web` commands +* `npm run minify-lib`: minifies the library code in `/lib` +* `npm run minify-web`: minifies the web code in `/weblib` ## Project Structure @@ -78,18 +89,19 @@ Useful helpers can also be found in `/scripts/core/*` and `/scripts/svg/*`. The live editor (index.htm & editor-dev.htm) uses the source in -`/scripts/editor.js` and `/scripts/interface/*`. Other pages use -sources in the root of `/scripts` as their entry-points. +`/web/editor.mjs` and `/web/interface/*`. Other pages use sources in +the root of `/web` as their entry-points. ## Testing The testing library used here is [Jasmine](https://jasmine.github.io/). -All test files follow the naming convention of `_spec.js`, -and must be listed in `/scripts/spec.js`. Linting automatically applies -to all files with a `.js` extension. +All test files follow the naming convention of `_spec.mjs` +(commandline and browser) or `_webspec.mjs` (browser-only). Browser +tests must be listed in `/spec/support/browser_specs.mjs`. Linting +automatically applies to all files with a `.js` or `.mjs` extension. -You can run the tests by opening `test.htm` in a browser. +You can run the browser tests by opening `test.htm` in a browser. The current state of automated testing is: @@ -97,12 +109,12 @@ The current state of automated testing is: * `Parser` and `Generator` stages have a good level of testing * Rendering methods (SVG generation) have a minimal level of testing; there are some high-level tests in - `/scripts/sequence/SequenceDiagram_spec.js`, and a series of image - comparison tests in `/scripts/sequence/Readme_spec.js` (testing that + `/scripts/sequence/SequenceDiagram_spec.mjs`, and a series of image + comparison tests in `/scripts/sequence/Readme_spec.mjs` (testing that the readme screenshots roughly match the current behaviour). Finally - `/scripts/sequence/SequenceDiagram_visual_spec.js` uses coarse image + `/scripts/sequence/SequenceDiagram_visual_spec.mjs` uses coarse image comparison to test components and interactions using baseline SVGs - from `test-images`. + from `spec/images`. * The editor has a minimal level of testing. If you suspect a failing test is not related to your changes, you can @@ -129,7 +141,7 @@ polyfils are included. ### Testing & Linting Ensure that all unit tests are passing and files pass linting. This can -be done by opening `test.htm` in a browser and running `npm run lint` +be done by opening `test.htm` in a browser and running `npm test` in a command window. At a minimum, you should ensure that tests are passing in Google Chrome, but testing in other supported browsers is even better. @@ -150,7 +162,7 @@ npm run minify; ``` This will update the files in `/lib` and `/weblib`. The minified code -is a self-contained copy of the `/scripts/sequence/SequenceDiagram.js` +is a self-contained copy of the `/scripts/sequence/SequenceDiagram.mjs` script, with some boiler-plate added to allow loading into a page in a variety of ways. diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md index 46ce4cd..ea15337 100644 --- a/docs/PULL_REQUEST_TEMPLATE.md +++ b/docs/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ reference it with #issue-number. Checklist: (tick with [x]) -- [ ] Any new spec files are listed in `scripts/specs.js`. +- [ ] Any new spec files are listed in `spec/support/browser_specs.mjs`. - [ ] Tests are passing in Google Chrome. - [ ] Linting is passing (`npm run lint`) - [ ] No dead code is left behind. diff --git a/editor-dev.htm b/editor-dev.htm index bf366b4..934bff6 100644 --- a/editor-dev.htm +++ b/editor-dev.htm @@ -67,7 +67,7 @@ data-integrity="sha256-HYX1RusN7a369vYuOd1mGvxLcNL4z/MihkahAI2CH8k=" > - + diff --git a/lib/sequence-diagram.js b/lib/sequence-diagram.js index fc2030d..8cb854e 100644 --- a/lib/sequence-diagram.js +++ b/lib/sequence-diagram.js @@ -1239,8 +1239,19 @@ } } + const nodejs = (typeof window === 'undefined'); + // Thanks, https://stackoverflow.com/a/23522755/1180785 - const safari = (/^((?!chrome|android).)*safari/i).test(navigator.userAgent); + const safari = ( + !nodejs && + (/^((?!chrome|android).)*safari/i).test(window.navigator.userAgent) + ); + + // Thanks, https://stackoverflow.com/a/9851769/1180785 + const firefox = ( + !nodejs && + typeof window.InstallTrigger !== 'undefined' + ); class Exporter { constructor() { @@ -6620,9 +6631,6 @@ } } - // Thanks, https://stackoverflow.com/a/9851769/1180785 - const firefox = (typeof window.InstallTrigger !== 'undefined'); - function merge(state, newState) { for(const k in state) { if(Object.prototype.hasOwnProperty.call(state, k)) { @@ -7115,16 +7123,16 @@ return this.dom.el(tag, namespace); } - box(attrs, position) { - return this.el('rect').attrs(attrs).attrs(position); + box(attrs, {height, width, x, y}) { + return this.el('rect').attrs(attrs).attrs({height, width, x, y}); } boxFactory(attrs) { return this.box.bind(this, attrs); } - line(attrs, position) { - return this.el('line').attrs(attrs).attrs(position); + line(attrs, {x1, x2, y1, y2}) { + return this.el('line').attrs(attrs).attrs({x1, x2, y1, y2}); } lineFactory(attrs) { @@ -7160,11 +7168,11 @@ return this.cross.bind(this, attrs); } - note(attrs, flickAttrs, position) { - const x0 = position.x; - const x1 = position.x + position.width; - const y0 = position.y; - const y1 = position.y + position.height; + note(attrs, flickAttrs, {height, width, x, y}) { + const x0 = x; + const x1 = x + width; + const y0 = y; + const y1 = y + height; const flick = 7; return this.el('g').add( @@ -7191,13 +7199,13 @@ return this.note.bind(this, attrs, flickAttrs); } - formattedText(attrs = {}, formatted = [], position = {}) { + formattedText(attrs = {}, formatted = [], {x, y} = {}) { const container = this.el('g'); const txt = new SVGTextBlock(container, this, { attrs, formatted, - x: position.x, - y: position.y, + x, + y, }); return Object.assign(container, { set: (state) => txt.set(state), @@ -9544,7 +9552,9 @@ function pickDocument(container) { if(container) { - return container.ownerDocument; + return container.ownerDocument || null; + } else if(typeof window === 'undefined') { + return null; } else { return window.document; } @@ -9921,6 +9931,10 @@ convert(els); } + function getDefaultThemeNames() { + return themes.map((theme) => theme.name); + } + Object.assign(SequenceDiagram, { Exporter, Generator, @@ -9930,6 +9944,7 @@ convert, convertAll, extractCodeFromSVG, + getDefaultThemeNames, registerCodeMirrorMode, renderAll, themes, diff --git a/lib/sequence-diagram.min.js b/lib/sequence-diagram.min.js index 39b5c6b..8e10225 100644 --- a/lib/sequence-diagram.min.js +++ b/lib/sequence-diagram.min.js @@ -1 +1 @@ -!function(){"use strict";function t(t,e,n=null){if(null===n)return t.indexOf(e);for(let s=0;s=t.length)return void s.push(n.slice());const r=t[e];if(!Array.isArray(r))return n.push(r),i(t,e+1,n,s),void n.pop();for(let a=0;a{n.push(...e(t))}),n}function o(t,e){const n=Ft[t.type];return!(!n||t.type!==e.type)&&!n.check.some(n=>t[n]!==e[n])}function h(t,n){Ft[t.type].merge.forEach(s=>{e(t[s],n[s])})}function l(t,e){for(let n=0;n{for(let s=0;st);return n.forEach(t=>{const s=Ft[t];s&&n.every(e=>t===e||s.siblings.has(e))&&e.add(t)}),e}function c(t,e,n,s){l(s,s=>{if(!t.has(s.type)||!e.has(s.type))return!1;for(let t=0;t{"agent begin"===t.type&&(t.mode=e,n=!0)}),n}return!1}function p(t,e,n,r=null){s(t,e,wt.equals),s(t,n,wt.equals);let i=0,a=t.length;if(r){const e=r.map(e=>wt.indexOf(t,e)).filter(t=>-1!==t);i=e.reduce((t,e)=>Math.min(t,e),t.length),a=e.reduce((t,e)=>Math.max(t,e),i)+1}return t.splice(i,0,e),t.splice(a+1,0,n),{indexL:i,indexR:a+1}}function f(t,e=!1){return{type:"string",suggest:e,then:Object.assign({"":0},t)}}function m(t,e){return t.v===e.v&&t.prefix===e.prefix&&t.suffix===e.suffix&&t.q===e.q}function b(t,e,n){let s=n.suggest;return Array.isArray(s)||(s=[s]),a(s,s=>!1===s?[]:"object"==typeof s?s.known?t["known"+s.known]||[]:[s]:"string"==typeof s&&s?[{v:s,q:""===e}]:[function(t,e){return Object.keys(e.then).length>0?{v:t,suffix:" ",q:!1}:{v:t,suffix:"\n",q:!1}}(e,n)])}function y(t,n){const s=[],i=r(n);return Object.keys(i.then).forEach(r=>{let a=i.then[r];"number"==typeof a&&(a=n[n.length-a-1]),e(s,b(t,r,a),m)}),s}function x(t,n,s,{suggest:r,override:i}){let a=null;"object"==typeof r&&r.known&&(a=r.known),n.type&&a!==n.type&&(i&&(n.type=i),e(t["known"+n.type],[{v:n.value,suffix:" ",q:!0}],m),n.type="",n.value=""),a&&(n.type=a,n.value&&(n.value+=s.s),n.value+=s.v)}function k(t,e,n){const s={type:"",value:""};let i=n;const a=[i];return t.line.forEach((e,n)=>{n===t.line.length-1&&(t.completions=y(t,a));const o=e.q?"":e.v;let h=i.then[o];void 0===h?(h=i.then[""],t.isVar=!0):t.isVar=e.q,"number"==typeof h?a.length-=h:a.push(h||Dt),i=r(a),x(t,s,e,i)}),e&&x(t,s,null,{}),t.nextCompletions=y(t,a),t.valid=Boolean(i.then["\n"])||0===Object.keys(i.then).length,i.type}function w(t){const e=t.baseToken||{};return{value:e.v||"",quoted:e.q||!1}}function v(t,e,n){return e.lastIndex=n,e.exec(t)}function A(t,e,n){return n?function(t,e,n){if(n.escape){const s=v(t,n.escape,e);if(s)return{appendSpace:"",appendValue:n.escapeWith(s),end:!1,newBlock:null,skip:s[0].length}}const s=v(t,n.end,e);return s?{appendSpace:"",appendValue:n.includeEnd?s[0]:"",end:!0,newBlock:null,skip:s[0].length}:{appendSpace:"",appendValue:t[e],end:!1,newBlock:null,skip:1}}(t,e,n):function(t,e){for(let n=0;n"}function R(t,e,n){const s=" "+t+" ";let r=-1,i=s.length,a=0;return Pt.forEach(({begin:t,end:o},h)=>{const l=n[h]?o:t;l.lastIndex=e;const d=l.exec(s);d&&(d.indexa)&&(r=h,i=d.index,a=l.lastIndex)}),{end:a-1,start:i,styleIndex:r}}function C(t,e){if(!t)return null;const n={};return e.forEach((t,e)=>{t&&Object.assign(n,Pt[e].attrs)}),n}function E(t){if(!t)return[];const e=Pt.map(()=>!1);let n=0,s=null;const r=[];return t.split("\n").forEach(t=>{const i=[];let a=0;for(;;){const{styleIndex:r,start:o,end:h}=R(t,a,e);if(-1===r)break;e[r]?(e[r]=!1,--n):(e[r]=!0,++n),o>a&&i.push({attrs:s,text:t.substring(a,o)}),s=C(n,e),a=h}a=n)&&(a=n),e>=a&&!i){let n=t[e];throw n||(n={b:r(t).e}),G("Missing agent name",n)}return{name:I(t,e,a),alias:I(t,a+1,n)}}(t,h,n,{enableAlias:i,allowBlankName:l});return{name:d,alias:g,flags:d?a:o}}function O(t,e,n,s){const r=[];let i=-1;for(let a=e;a{const r=t.get(e);(null===n||r.indexs.index)&&(s=r)}),{left:n.id,right:s.id}}function P(t=null,e=null){return null===t?e:null===e?t:Math.max(t,e)}function j(t,n){return e(t.agentIDs,n.agentIDs),{agentIDs:t.agentIDs,asynchronousY:P(t.asynchronousY,n.asynchronousY),topShift:Math.max(t.topShift,n.topShift),y:P(t.y,n.y)}}function q(t){return null===t?null:t.element?t.element:t}function Y(t,e,n){if(!Array.isArray(n))throw new Error("Invalid formatted text line: "+n);n.forEach(({text:n,attrs:s})=>{s?e.add(t.el("tspan").attrs(s).add(n)):e.add(n)})}function W(t,e){let n=null,s=null;return e.forEach(e=>{const r=t.get(e);(null===n||r.indexs.index)&&(s=r)}),{left:n.id,right:s.id}}function U(t,e){return t.v===e.v&&t.prefix===e.prefix&&t.suffix===e.suffix&&t.q===e.q}function X(t,e,n){const s=t.getLine(e),r={squash:{ch:n,line:e},word:{ch:n,line:e}};return n>0&&" "===s[n-1]&&($e.after.includes(s[n-2])&&r.word.ch--,r.squash.ch--),r}function Q(t,e,n){const s=function({v:t,q:e,prefix:n="",suffix:s=""},r){const i=r||!en.test(t)?r:'"';return n+(i&&e?i+t.replace(nn,"\\$&")+i:t)+s}(t,n),r=t.q?e.fromVar:e.fromKey;return"\n"===s?{className:"pick-virtual",displayFrom:null,displayText:"",from:r.squash,text:"\n",to:e.to.squash}:{className:null,displayFrom:r.word,displayText:s.trim(),from:$e.start.test(s)?r.squash:r.word,text:s,to:$e.end.test(s)?e.to.squash:e.to.word}}function J({global:t,prefix:e="",suffix:n=""},s){const r=s[t];return r?r.map(t=>({prefix:e,q:!0,suffix:n,v:t})):[]}function Z(t,n){const s=t.getCursor(),i=function(t,e){const n=t.getLineTokens(e.line);for(let t=0;t=e.ch){n.length=t+1;break}return n}(t,s),a=r(i)||t.getTokenAt(s),o=function(t,e){let n="",s=0,r=0;t.forEach(t=>{t.state.isVar?(n+=t.string,r=t.end):(n="",s=t.end)}),r>e.ch&&(n=n.substr(0,e.ch-s));const i=_e.exec(n);n=i[2];let a="";return tn.test(n)&&(a=n.charAt(0),n=n.substr(1)),{from:s+i[1].length,partial:n,quote:a,valid:r>=s}}(i,s),h=function(t,e){let n=t.string;t.end>e.ch&&(n=n.substr(0,e.ch-t.start));const s=_e.exec(n);return{from:t.start+s[1].length,partial:s[2],valid:!0}}(a,s),l=s.ch>0&&a.state.line.length>0;let d=l?a.state.completions:a.state.beginCompletions;l||(d=d.concat(a.state.knownAgent)),function(t,n={}){for(let s=0;s(t.q||!o.quote)&&function(t,e){return e.valid&&t.startsWith(e.partial)}(t.v,t.q?o:h)).map(t=>n.completeSingle||t.v!==(t.q?o:h).partial?Q(t,g,o.quote):(c=t,null)).filter(t=>null!==t);return c&&u.length>0&&u.unshift(Q(c,g,o.quote)),{from:function(t,e){let n=null;return t.forEach(({displayFrom:t})=>{t&&(!n||t.line>n.line||t.line===n.line&&t.ch>n.ch)&&(n=t)}),n||e.word}(u,g.fromKey),list:u,to:g.to.word}}function K(t,e="sequence"){const n=t||window.CodeMirror;n.defineMode(e,()=>on),n.registerHelper("hint",e,Z)}function _(t){const e=(new DOMParser).parseFromString(t,"image/svg+xml").querySelector("metadata");return e?e.textContent:""}function $(t){function e(t,e){n.push(e)}const n=[];if(t.forEach(t=>{t.addEventListener("error",e),t.optimisedRenderPreReflow()}),t.forEach(t=>{t.optimisedRenderReflow()}),t.forEach(t=>{t.optimisedRenderPostReflow(),t.removeEventListener("error",e)}),n.length>0)throw n}function tt(t,e=null,n={}){if("svg"===t.tagName)return null;const s=function(t){return{interactive:function(t){return void 0!==t&&"false"!==t}(t.dataset.sdInteractive),namespace:t.dataset.sdNamespace||null}}(t),r=new hn(null===e?t.textContent:e,Object.assign(s,n)),i=r.dom(),a=t.attributes;for(let t=0;ttt(t,s,e));return!1!==r.render&&$(n),n}return tt(t,s,r)}class nt{constructor(t,e){Array.isArray(e)?this.deltas=e:this.deltas=[0,2*-e/3,-e,2*-e/3,0,2*e/3,e,2*e/3],this.partWidth=t/this.deltas.length}getDelta(t){return this.deltas[t%this.deltas.length]}}class st{constructor(t){this.svg=t}reset(){}addDefs(){}getBlock(t){return this.blocks[t]||this.blocks[""]}getNote(t){return this.notes[t]||this.notes[""]}getDivider(t){return this.dividers[t]||this.dividers[""]}optionsAttributes(t,e){return function(t,e){const n=Object.assign({},t[""]);return e.forEach(e=>{Object.assign(n,t[e]||{})}),n}(t,e)}renderAgentLine({x:t,y0:e,y1:n,width:s,className:r,options:i}){const a=this.optionsAttributes(this.agentLineAttrs,i);return s>0?this.svg.box(a,{x:t-s/2,y:e,width:s,height:n-e}).addClass(r):this.svg.line(a,{x1:t,x2:t,y1:e,y2:n}).addClass(r)}renderArrowHead(t,{x:e,y:n,width:s,height:r,dir:i}){const a=s*i.dx,o=s*i.dy,h=.5*r*i.dx,l=.5*-r*i.dy;return this.svg.el("none"===t.fill?"polyline":"polygon").attr("points",e+a-l+" "+(n+o-h)+" "+e+" "+n+" "+(e+a+l)+" "+(n+o+h)).attrs(t)}renderTag(t,{x:e,y:n,width:s,height:r}){const{rx:i,ry:a}=t,o=e+s,h=n+r,l="M"+o+" "+n+"L"+o+" "+(h-a)+"L"+(o-i)+" "+h+"L"+e+" "+h,d=this.svg.el("g");return"none"!==t.fill&&d.add(this.svg.el("path").attr("d",l+"L"+e+" "+n).attrs(t).attr("stroke","none")),"none"!==t.stroke&&d.add(this.svg.el("path").attr("d",l).attrs(t).attr("fill","none")),d}renderDB(t,e){const n=t["db-z"];return this.svg.el("g").add(this.svg.box({rx:e.width/2,ry:n},e).attrs(t),this.svg.el("path").attr("d","M"+e.x+" "+(e.y+n)+"a"+e.width/2+" "+n+" 0 0 0 "+e.width+" 0").attrs(t).attr("fill","none"))}renderRef(t,e){return{shape:this.svg.box(t,e).attrs({fill:"none"}),mask:this.svg.box(t,e).attrs({fill:"#000000",stroke:"none"}),fill:this.svg.box(t,e).attrs({stroke:"none"})}}renderFlatConnect(t,e,{x1:n,y1:s,x2:r,y2:i}){return{shape:this.svg.el("path").attr("d",this.svg.patternedLine(t).move(n,s).line(r,i).cap().asPath()).attrs(e),p1:{x:n,y:s},p2:{x:r,y:i}}}renderRevConnect(t,e,{x1:n,y1:s,x2:r,y2:i,xR:a,rad:o}){const h=(i-s)/2,l=this.svg.patternedLine(t).move(n,s).line(a,s);return o0?this.svg.el("g").add(this.svg.line({fill:"none"},{x1:e,x2:e+(r-s)/2,y1:o,y2:o}).attrs(t),this.svg.line({fill:"none"},{x1:e+(r+s)/2,x2:e+r,y1:o,y2:o}).attrs(t)):this.svg.line({fill:"none"},{x1:e,x2:e+r,y1:o,y2:o}).attrs(t),{shape:a}}renderDelayDivider({dotSize:t,gapSize:e},{x:n,y:s,width:r,height:i}){const a=this.svg.el("g");for(let o=0;o+e<=i;o+=t+e)a.add(this.svg.box({fill:"#000000"},{x:n,y:s+o,width:r,height:e}));return{mask:a}}renderTearDivider({fadeBegin:t,fadeSize:e,pattern:n,zigWidth:s,zigHeight:r,lineAttrs:i},{x:a,y:o,labelWidth:h,labelHeight:l,width:d,height:g,env:c}){const u=c.addDef("tear-grad",()=>{const n=100/d;return this.svg.linearGradient({},[{offset:t*n+"%","stop-color":"#000000"},{offset:(t+e)*n+"%","stop-color":"#FFFFFF"},{offset:100-(t+e)*n+"%","stop-color":"#FFFFFF"},{offset:100-t*n+"%","stop-color":"#000000"}])}),p=this.svg.el("mask").attr("maskUnits","userSpaceOnUse").add(this.svg.box({fill:"url(#"+u+")"},{x:a,y:o-5,width:d,height:g+10})),f=c.addDef(p);h>0&&p.add(this.svg.box({rx:2,ry:2,fill:"#000000"},{x:a+(d-h)/2,y:o+(g-l)/2-1,width:h,height:l+2}));const m=n||new nt(s,[r,-r]);let b=null;const y=this.svg.patternedLine(m).move(a,o).line(a+d,o),x=this.svg.el("g").attr("mask","url(#"+f+")").add(this.svg.el("path").attrs({d:y.asPath(),fill:"none"}).attrs(i));if(g>0){const t=this.svg.patternedLine(m).move(a,o+g).line(a+d,o+g);x.add(this.svg.el("path").attrs({d:t.asPath(),fill:"none"}).attrs(i)),y.line(t.x,t.y,{patterned:!1}).cap(),y.points.push(...t.points.reverse()),b=this.svg.el("path").attrs({d:y.asPath(),fill:"#000000"})}return{shape:x,mask:b}}}const rt="sans-serif",it=new nt(6,.5),at={"font-family":rt,"font-size":8,"line-height":1.3},ot={"font-family":rt,"font-size":8,"line-height":1.3,"text-anchor":"middle"};class ht extends st{constructor(t){super(t);const e={padding:{top:3,bottom:2},tag:{padding:{top:1,left:3,right:3,bottom:0},boxRenderer:this.renderTag.bind(this,{fill:"#FFFFFF",stroke:"#000000","stroke-width":1,rx:2,ry:2}),labelAttrs:{"font-family":rt,"font-weight":"bold","font-size":9,"line-height":1.3,"text-anchor":"left"}},label:{minHeight:4,padding:{top:1,left:5,right:3,bottom:1},labelAttrs:{"font-family":rt,"font-size":8,"line-height":1.3,"text-anchor":"left"}}};Object.assign(this,{titleMargin:10,outerMargin:5,agentMargin:10,actionMargin:10,minActionMargin:3,agentLineHighlightRadius:4,agentCap:{box:{padding:{top:5,left:10,right:10,bottom:5},arrowBottom:12.8,boxAttrs:{fill:"#FFFFFF",stroke:"#000000","stroke-width":1},labelAttrs:{"font-family":rt,"font-size":12,"line-height":1.3,"text-anchor":"middle"}},database:{padding:{top:12,left:10,right:10,bottom:3},arrowBottom:12.8,boxRenderer:this.renderDB.bind(this,{fill:"#FFFFFF",stroke:"#000000","stroke-width":1,"db-z":5}),labelAttrs:{"font-family":rt,"font-size":12,"line-height":1.3,"text-anchor":"middle"}},cross:{size:20,render:t.crossFactory({fill:"none",stroke:"#000000","stroke-width":1})},bar:{height:4,render:t.boxFactory({fill:"#000000",stroke:"#000000","stroke-width":1})},fade:{width:5,height:6,extend:1},none:{height:10}},connect:{loopbackRadius:6,line:{solid:{attrs:{fill:"none",stroke:"#000000","stroke-width":1},renderFlat:this.renderFlatConnect.bind(this,null),renderRev:this.renderRevConnect.bind(this,null)},dash:{attrs:{fill:"none",stroke:"#000000","stroke-width":1,"stroke-dasharray":"4, 2"},renderFlat:this.renderFlatConnect.bind(this,null),renderRev:this.renderRevConnect.bind(this,null)},wave:{attrs:{fill:"none",stroke:"#000000","stroke-width":1,"stroke-linejoin":"round","stroke-linecap":"round"},renderFlat:this.renderFlatConnect.bind(this,it),renderRev:this.renderRevConnect.bind(this,it)}},arrow:{single:{width:5,height:10,render:this.renderArrowHead.bind(this),attrs:{fill:"#000000","stroke-width":0,"stroke-linejoin":"miter"}},double:{width:4,height:6,render:this.renderArrowHead.bind(this),attrs:{fill:"none",stroke:"#000000","stroke-width":1,"stroke-linejoin":"miter"}},cross:{short:7,radius:3,render:t.crossFactory({fill:"none",stroke:"#000000","stroke-width":1})}},label:{padding:6,margin:{top:2,bottom:1},attrs:{"font-family":rt,"font-size":8,"line-height":1.3,"text-anchor":"middle"},loopbackAttrs:{"font-family":rt,"font-size":8,"line-height":1.3}},source:{radius:2,render:t.circleFactory({fill:"#000000",stroke:"#000000","stroke-width":1})},mask:{padding:{top:0,left:3,right:3,bottom:1}}},titleAttrs:{"font-family":rt,"font-size":20,"line-height":1.3,"text-anchor":"middle",class:"title"},agentLineAttrs:{"":{fill:"none",stroke:"#000000","stroke-width":1},red:{stroke:"#CC0000"}},blocks:{ref:{margin:{top:0,bottom:0},boxRenderer:this.renderRef.bind(this,{fill:"#FFFFFF",stroke:"#000000","stroke-width":1.5,rx:2,ry:2}),section:e},"":{margin:{top:0,bottom:0},boxRenderer:t.boxFactory({fill:"none",stroke:"#000000","stroke-width":1.5,rx:2,ry:2}),collapsedBoxRenderer:this.renderRef.bind(this,{fill:"#FFFFFF",stroke:"#000000","stroke-width":1.5,rx:2,ry:2}),section:e,sepRenderer:t.lineFactory({stroke:"#000000","stroke-width":1.5,"stroke-dasharray":"4, 2"})}},notes:{text:{margin:{top:0,left:2,right:2,bottom:0},padding:{top:2,left:2,right:2,bottom:2},overlap:{left:10,right:10},boxRenderer:t.boxFactory({fill:"#FFFFFF"}),labelAttrs:at},note:{margin:{top:0,left:5,right:5,bottom:0},padding:{top:5,left:5,right:10,bottom:5},overlap:{left:10,right:10},boxRenderer:t.noteFactory({fill:"#FFFFFF",stroke:"#000000","stroke-width":1},{fill:"none",stroke:"#000000","stroke-width":1}),labelAttrs:at},state:{margin:{top:0,left:5,right:5,bottom:0},padding:{top:7,left:7,right:7,bottom:7},overlap:{left:10,right:10},boxRenderer:t.boxFactory({fill:"#FFFFFF",stroke:"#000000","stroke-width":1,rx:10,ry:10}),labelAttrs:at}},dividers:{"":{labelAttrs:ot,padding:{top:2,left:5,right:5,bottom:2},extend:0,margin:0,render:()=>({})},line:{labelAttrs:ot,padding:{top:2,left:5,right:5,bottom:2},extend:10,margin:0,render:this.renderLineDivider.bind(this,{lineAttrs:{stroke:"#000000"}})},delay:{labelAttrs:ot,padding:{top:2,left:5,right:5,bottom:2},extend:0,margin:0,render:this.renderDelayDivider.bind(this,{dotSize:1,gapSize:2})},tear:{labelAttrs:ot,padding:{top:2,left:5,right:5,bottom:2},extend:10,margin:10,render:this.renderTearDivider.bind(this,{fadeBegin:5,fadeSize:10,zigWidth:6,zigHeight:1,lineAttrs:{stroke:"#000000"}})}}})}}class lt{constructor(){this.name="basic"}build(t){return new ht(t)}}const dt="sans-serif",gt=new nt(10,1),ct={"font-family":dt,"font-size":8,"line-height":1.3},ut={"font-family":dt,"font-size":8,"line-height":1.3,"text-anchor":"middle"};class pt extends st{constructor(t){super(t);const e={padding:{top:3,bottom:4},tag:{padding:{top:2,left:5,right:5,bottom:1},boxRenderer:this.renderTag.bind(this,{fill:"#FFFFFF",stroke:"#000000","stroke-width":2,rx:3,ry:3}),labelAttrs:{"font-family":dt,"font-weight":"bold","font-size":9,"line-height":1.3,"text-anchor":"left"}},label:{minHeight:5,padding:{top:2,left:5,right:3,bottom:1},labelAttrs:{"font-family":dt,"font-size":8,"line-height":1.3,"text-anchor":"left"}}};Object.assign(this,{titleMargin:12,outerMargin:5,agentMargin:8,actionMargin:5,minActionMargin:5,agentLineHighlightRadius:4,agentCap:{box:{padding:{top:1,left:3,right:3,bottom:1},arrowBottom:11.1,boxAttrs:{fill:"#FFFFFF",stroke:"#000000","stroke-width":3,rx:4,ry:4},labelAttrs:{"font-family":dt,"font-weight":"bold","font-size":14,"line-height":1.3,"text-anchor":"middle"}},database:{padding:{top:4,left:3,right:3,bottom:0},arrowBottom:11.1,boxRenderer:this.renderDB.bind(this,{fill:"#FFFFFF",stroke:"#000000","stroke-width":3,"db-z":2}),labelAttrs:{"font-family":dt,"font-weight":"bold","font-size":14,"line-height":1.3,"text-anchor":"middle"}},cross:{size:20,render:t.crossFactory({fill:"none",stroke:"#000000","stroke-width":3,"stroke-linecap":"round"})},bar:{height:4,render:t.boxFactory({fill:"#000000",stroke:"#000000","stroke-width":3,rx:2,ry:2})},fade:{width:5,height:10,extend:1},none:{height:10}},connect:{loopbackRadius:8,line:{solid:{attrs:{fill:"none",stroke:"#000000","stroke-width":3},renderFlat:this.renderFlatConnect.bind(this,null),renderRev:this.renderRevConnect.bind(this,null)},dash:{attrs:{fill:"none",stroke:"#000000","stroke-width":3,"stroke-dasharray":"10, 4"},renderFlat:this.renderFlatConnect.bind(this,null),renderRev:this.renderRevConnect.bind(this,null)},wave:{attrs:{fill:"none",stroke:"#000000","stroke-width":3,"stroke-linejoin":"round","stroke-linecap":"round"},renderFlat:this.renderFlatConnect.bind(this,gt),renderRev:this.renderRevConnect.bind(this,gt)}},arrow:{single:{width:10,height:12,render:this.renderArrowHead.bind(this),attrs:{fill:"#000000",stroke:"#000000","stroke-width":3,"stroke-linejoin":"round"}},double:{width:10,height:12,render:this.renderArrowHead.bind(this),attrs:{fill:"none",stroke:"#000000","stroke-width":3,"stroke-linejoin":"round","stroke-linecap":"round"}},cross:{short:10,radius:5,render:t.crossFactory({fill:"none",stroke:"#000000","stroke-width":3,"stroke-linejoin":"round","stroke-linecap":"round"})}},label:{padding:7,margin:{top:2,bottom:3},attrs:{"font-family":dt,"font-size":8,"line-height":1.3,"text-anchor":"middle"},loopbackAttrs:{"font-family":dt,"font-size":8,"line-height":1.3}},source:{radius:5,render:t.circleFactory({fill:"#000000",stroke:"#000000","stroke-width":3})},mask:{padding:{top:1,left:5,right:5,bottom:3}}},titleAttrs:{"font-family":dt,"font-weight":"bolder","font-size":20,"line-height":1.3,"text-anchor":"middle",class:"title"},agentLineAttrs:{"":{fill:"none",stroke:"#000000","stroke-width":3},red:{stroke:"#DD0000"}},blocks:{ref:{margin:{top:0,bottom:0},boxRenderer:this.renderRef.bind(this,{fill:"#FFFFFF",stroke:"#000000","stroke-width":4,rx:5,ry:5}),section:e},"":{margin:{top:0,bottom:0},boxRenderer:t.boxFactory({fill:"none",stroke:"#000000","stroke-width":4,rx:5,ry:5}),collapsedBoxRenderer:this.renderRef.bind(this,{fill:"#FFFFFF",stroke:"#000000","stroke-width":4,rx:5,ry:5}),section:e,sepRenderer:t.lineFactory({stroke:"#000000","stroke-width":2,"stroke-dasharray":"5, 3"})}},notes:{text:{margin:{top:0,left:2,right:2,bottom:0},padding:{top:2,left:2,right:2,bottom:2},overlap:{left:10,right:10},boxRenderer:t.boxFactory({fill:"#FFFFFF"}),labelAttrs:ct},note:{margin:{top:0,left:5,right:5,bottom:0},padding:{top:3,left:3,right:10,bottom:3},overlap:{left:10,right:10},boxRenderer:t.noteFactory({fill:"#FFFFFF",stroke:"#000000","stroke-width":2,"stroke-linejoin":"round"},{fill:"none",stroke:"#000000","stroke-width":1}),labelAttrs:ct},state:{margin:{top:0,left:5,right:5,bottom:0},padding:{top:5,left:7,right:7,bottom:5},overlap:{left:10,right:10},boxRenderer:t.boxFactory({fill:"#FFFFFF",stroke:"#000000","stroke-width":3,rx:10,ry:10}),labelAttrs:ct}},dividers:{"":{labelAttrs:ut,padding:{top:2,left:5,right:5,bottom:2},extend:0,margin:0,render:()=>({})},line:{labelAttrs:ut,padding:{top:2,left:5,right:5,bottom:2},extend:10,margin:0,render:this.renderLineDivider.bind(this,{lineAttrs:{stroke:"#000000","stroke-width":2,"stroke-linecap":"round"}})},delay:{labelAttrs:ut,padding:{top:2,left:5,right:5,bottom:2},extend:0,margin:0,render:this.renderDelayDivider.bind(this,{dotSize:3,gapSize:3})},tear:{labelAttrs:ut,padding:{top:2,left:5,right:5,bottom:2},extend:10,margin:10,render:this.renderTearDivider.bind(this,{fadeBegin:5,fadeSize:10,zigWidth:6,zigHeight:1,lineAttrs:{stroke:"#000000","stroke-width":2,"stroke-linejoin":"round"}})}}})}}class ft{constructor(){this.name="chunky"}build(t){return new pt(t)}}class mt{constructor(){this.listeners=new Map,this.forwards=new Set}addEventListener(t,e){const n=this.listeners.get(t);n?n.push(e):this.listeners.set(t,[e])}removeEventListener(t,e){const n=this.listeners.get(t);if(!n)return;const s=n.indexOf(e);-1!==s&&n.splice(s,1)}on(t,e){return this.addEventListener(t,e),this}off(t,e){return this.removeEventListener(t,e),this}countEventListeners(t){return(this.listeners.get(t)||[]).length}removeAllEventListeners(t){t?this.listeners.delete(t):this.listeners.clear()}addEventForwarding(t){this.forwards.add(t)}removeEventForwarding(t){this.forwards.delete(t)}removeAllEventForwardings(){this.forwards.clear()}trigger(t,e=[]){(this.listeners.get(t)||[]).forEach(t=>t(...e)),this.forwards.forEach(n=>n.trigger(t,e))}}const bt=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);class yt{constructor(){this.latestSVG=null,this.latestInternalSVG=null,this.canvas=null,this.context=null,this.indexPNG=0,this.latestPNGIndex=0,this.latestPNG=null}getSVGContent(t){let e=t.dom().outerHTML;return e=e.replace(/^{this.canvas.width=s,this.canvas.height=r,this.context.drawImage(i,0,0,s,r),a&&document.body.removeChild(a),n(this.canvas)};i.addEventListener("load",()=>{a?setTimeout(o,50):o()},{once:!0}),i.src=this.getSVGURL(t)}getPNGBlob(t,e,n){this.getCanvas(t,e,t=>{t.toBlob(n,"image/png")})}getPNGURL(t,e,n){++this.indexPNG;const s=this.indexPNG;this.getPNGBlob(t,e,t=>{const e=URL.createObjectURL(t);s>=this.latestPNGIndex?(this.latestPNG&&URL.revokeObjectURL(this.latestPNG),this.latestPNG=e,this.latestPNGIndex=s,n(e,!0)):(n(e,!1),URL.revokeObjectURL(e))})}}class xt{constructor({visible:t=!1,locked:e=!1,blocked:n=!1,highlighted:s=!1,group:r=null,covered:i=!1}={}){this.visible=t,this.locked=e,this.blocked=n,this.highlighted=s,this.group=r,this.covered=i}}xt.LOCKED=new xt({locked:!0}),xt.DEFAULT=new xt;const kt={equals:(t,e)=>t.name===e.name,hasFlag:(t,e=!0)=>n=>n.flags.includes(t)===e},wt={equals:(t,e)=>t.id===e.id,make:(t,{anchorRight:e=!1,isVirtualSource:n=!1}={})=>({anchorRight:e,id:t,isVirtualSource:n,options:[]}),indexOf:(e,n)=>t(e,n,wt.equals),hasIntersection:(e,n)=>(function(e,n,s=null){for(let r=0;r{const i=t(e,n,wt.equals);-1===i?e.push(s):e.splice(i+r,0,s)}},vt={"note over":[wt.make("["),wt.make("]")],"note left":[wt.make("[")],"note right":[wt.make("]")]},At=["[","]"],Ft={"agent begin":{check:["mode"],merge:["agentIDs"],siblings:new Set(["agent highlight"])},"agent end":{check:["mode"],merge:["agentIDs"],siblings:new Set(["agent highlight"])},"agent highlight":{check:["highlighted"],merge:["agentIDs"],siblings:new Set(["agent begin","agent end"])}};class Mt{constructor(){this.agentStates=new Map,this.agentAliases=new Map,this.activeGroups=new Map,this.gAgents=[],this.labelPattern=null,this.nextID=0,this.nesting=[],this.markers=new Set,this.currentSection=null,this.currentNest=null,this.stageHandlers={"block begin":this.handleBlockBegin.bind(this),"block split":this.handleBlockSplit.bind(this),"block end":this.handleBlockEnd.bind(this),"group begin":this.handleGroupBegin.bind(this),mark:this.handleMark.bind(this),async:this.handleAsync.bind(this),"agent define":this.handleAgentDefine.bind(this),"agent options":this.handleAgentOptions.bind(this),"agent begin":this.handleAgentBegin.bind(this),"agent end":this.handleAgentEnd.bind(this),divider:this.handleDivider.bind(this),"label pattern":this.handleLabelPattern.bind(this),connect:this.handleConnect.bind(this),"connect-delay-begin":this.handleConnectDelayBegin.bind(this),"connect-delay-end":this.handleConnectDelayEnd.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)},this.expandGroupedGAgent=this.expandGroupedGAgent.bind(this),this.handleStage=this.handleStage.bind(this),this.toGAgent=this.toGAgent.bind(this),this.endGroup=this.endGroup.bind(this)}toGAgent({name:t,alias:e,flags:n}){if(e){if(this.agentAliases.has(t))throw new Error("Cannot alias "+t+"; it is already an alias");const n=this.agentAliases.get(e);if(n&&n!==e||this.gAgents.some(t=>t.id===e))throw new Error("Cannot use "+e+" as an alias; it is already in use");this.agentAliases.set(e,t)}return wt.make(this.agentAliases.get(t)||t,{isVirtualSource:n.includes("source")})}addStage(t,e=!0){t&&(void 0===t.ln&&(t.ln=this.latestLine),this.currentSection.stages.push(t),e&&(this.currentNest.hasContent=!0))}addParallelStages(t){const e=t.filter(t=>Boolean(t));0!==e.length&&(1!==e.length?(e.forEach(t=>{void 0===t.ln&&(t.ln=this.latestLine)}),this.addStage({type:"parallel",stages:e})):this.addStage(e[0]))}defineGAgents(t){e(this.currentNest.gAgents,t.filter(t=>!At.includes(t.id)),wt.equals),e(this.gAgents,t,wt.equals)}getGAgentState(t){return this.agentStates.get(t.id)||xt.DEFAULT}updateGAgentState(t,e){const n=this.agentStates.get(t.id);n?Object.assign(n,e):this.agentStates.set(t.id,new xt(e))}replaceGAgentState(t,e){this.agentStates.set(t.id,e)}validateGAgents(t,{allowGrouped:e=!1,allowCovered:n=!1,allowVirtual:s=!1}={}){t.forEach(t=>{const r=this.getGAgentState(t);if(r.blocked&&null===r.group)throw new Error("Duplicate agent name: "+t.id);if(!n&&r.covered)throw new Error("Agent "+t.id+" is hidden behind group");if(!e&&null!==r.group)throw new Error("Agent "+t.id+" is in a group");if(!s&&t.isVirtualSource)throw new Error("cannot use message source here");if(t.id.startsWith("__"))throw new Error(t.id+" is a reserved name")})}setGAgentVis(t,e,n,s=!1){const r=new Set,i=t.filter(t=>{if(r.has(t.id))return!1;r.add(t.id);const n=this.getGAgentState(t);if(n.locked||n.blocked){if(s)throw new Error("Cannot begin/end agent: "+t.id);return!1}return n.visible!==e});return 0===i.length?null:(i.forEach(t=>{this.updateGAgentState(t,{visible:e})}),this.defineGAgents(i),{type:e?"agent begin":"agent end",agentIDs:i.map(t=>t.id),mode:n})}setGAgentHighlight(t,e,n=!1){const s=t.filter(t=>{const s=this.getGAgentState(t);if(s.locked||s.blocked){if(n)throw new Error("Cannot highlight agent: "+t.id);return!1}return s.visible&&s.highlighted!==e});return 0===s.length?null:(s.forEach(t=>{this.updateGAgentState(t,{highlighted:e})}),{type:"agent highlight",agentIDs:s.map(t=>t.id),highlighted:e})}_makeSection(t,e){return{header:t,delayedConnections:new Map,stages:e}}_checkSectionEnd(){const t=this.currentSection.delayedConnections;if(t.size>0){const e=t.values().next().value;throw new Error('Unused delayed connection "'+e.tag+'" at line '+(e.ln+1))}}beginNested(t,{tag:e,label:n,name:s,ln:r}){const i=wt.make(s+"[",{anchorRight:!0}),a=wt.make(s+"]"),o=[i,a],h=[];return this.currentSection=this._makeSection({type:"block begin",blockType:t,tag:this.textFormatter(e),label:this.textFormatter(n),canHide:!0,left:i.id,right:a.id,ln:r},h),this.currentNest={blockType:t,gAgents:o,leftGAgent:i,rightGAgent:a,hasContent:!1,sections:[this.currentSection]},this.replaceGAgentState(i,xt.LOCKED),this.replaceGAgentState(a,xt.LOCKED),this.nesting.push(this.currentNest),{stages:h}}nextBlockName(){const t="__BLOCK"+this.nextID;return++this.nextID,t}nextVirtualAgentName(){const t="__"+this.nextID;return++this.nextID,t}handleBlockBegin({ln:t,blockType:e,tag:n,label:s}){this.beginNested(e,{tag:n,label:s,name:this.nextBlockName(),ln:t})}handleBlockSplit({ln:t,blockType:e,tag:n,label:s}){if("if"!==this.currentNest.blockType)throw new Error('Invalid block nesting ("else" inside '+this.currentNest.blockType+")");this._checkSectionEnd(),this.currentSection=this._makeSection({type:"block split",blockType:e,tag:this.textFormatter(n),label:this.textFormatter(s),left:this.currentNest.leftGAgent.id,right:this.currentNest.rightGAgent.id,ln:t},[]),this.currentNest.sections.push(this.currentSection)}handleBlockEnd(){if(this.nesting.length<=1)throw new Error('Invalid block nesting (too many "end"s)');this._checkSectionEnd();const t=this.nesting.pop();if(this.currentNest=r(this.nesting),this.currentSection=r(this.currentNest.sections),!t.hasContent)throw new Error("Empty block");this.defineGAgents(t.gAgents),p(this.gAgents,t.leftGAgent,t.rightGAgent,t.gAgents),t.sections.forEach(t=>{this.currentSection.stages.push(t.header),this.currentSection.stages.push(...t.stages)}),this.addStage({type:"block end",left:t.leftGAgent.id,right:t.rightGAgent.id})}makeGroupDetails(t,e){const s=t.map(this.toGAgent);if(this.validateGAgents(s),this.agentStates.has(e))throw new Error("Duplicate agent name: "+e);const r=this.nextBlockName(),i=wt.make(r+"[",{anchorRight:!0}),a=wt.make(r+"]");this.replaceGAgentState(i,xt.LOCKED),this.replaceGAgentState(a,xt.LOCKED),this.updateGAgentState(wt.make(e),{blocked:!0,group:e}),this.defineGAgents([...s,i,a]);const{indexL:o,indexR:h}=p(this.gAgents,i,a,s),l=[],d=s.slice();for(let t=o+1;t{this.updateGAgentState(t,{group:r})}),i.gAgentsCovered.forEach(t=>{this.updateGAgentState(t,{covered:!0})}),this.activeGroups.set(r,i),this.addStage(this.setGAgentVis(i.gAgents,!0,"box")),this.addStage({type:"block begin",blockType:e,tag:this.textFormatter(n),canHide:!1,label:this.textFormatter(s),left:i.leftGAgent.id,right:i.rightGAgent.id})}endGroup({name:t}){const e=this.activeGroups.get(t);return e?(this.activeGroups.delete(t),e.gAgentsContained.forEach(t=>{this.updateGAgentState(t,{group:null})}),e.gAgentsCovered.forEach(t=>{this.updateGAgentState(t,{covered:!1})}),this.updateGAgentState(wt.make(t),{group:null}),{type:"block end",left:e.leftGAgent.id,right:e.rightGAgent.id}):null}handleMark({name:t}){this.markers.add(t),this.addStage({type:"mark",name:t},!1)}handleDivider({mode:t,height:e,label:n}){this.addStage({type:"divider",mode:t,height:e,formattedLabel:this.textFormatter(n)},!1)}handleAsync({target:t}){if(""!==t&&!this.markers.has(t))throw new Error("Unknown marker: "+t);this.addStage({type:"async",target:t},!1)}handleLabelPattern({pattern:t}){this.labelPattern=t.slice();for(let t=0;t{"string"==typeof t?e+=t:void 0!==t.token?e+=n[t.token]:void 0!==t.current&&(e+=t.current.toFixed(t.dp),t.current+=t.inc)}),e}expandGroupedGAgent(t){const{group:e}=this.getGAgentState(t);if(!e)return[t];const n=this.activeGroups.get(e);return[n.leftGAgent,n.rightGAgent]}expandGroupedGAgentConnection(t){const e=this.expandGroupedGAgent(t[0]),n=this.expandGroupedGAgent(t[1]);let s=wt.indexOf(this.gAgents,e[0]),i=wt.indexOf(this.gAgents,n[0]);return-1===s&&(s=e[0].isVirtualSource?-1:this.gAgents.length),-1===i&&(i=this.gAgents.length),s===i?[r(e),r(n)]:s!t.isVirtualSource));const s=t.filter(kt.hasFlag("begin",!1)).map(this.toGAgent).filter(t=>!t.isVirtualSource);return this.addStage(this.setGAgentVis(s,!0,"box")),{flags:e,gAgents:n}}_makeConnectParallelStages(t,e){return[this.setGAgentVis(t.beginGAgents,!0,"box",!0),this.setGAgentHighlight(t.startGAgents,!0,!0),e,this.setGAgentHighlight(t.stopGAgents,!1,!0),this.setGAgentVis(t.endGAgents,!1,"cross",!0)]}_isSelfConnect(t){const e=t.map(this.toGAgent),n=this.expandGroupedGAgentConnection(e);return n[0].id===n[1].id&&!n.some(t=>t.isVirtualSource)}handleConnect({agents:t,label:e,options:n}){if(this._isSelfConnect(t)){const s={};return this.handleConnectDelayBegin({agent:t[0],tag:s,options:n,ln:0}),void this.handleConnectDelayEnd({agent:t[1],tag:s,label:e,options:n})}let{flags:s,gAgents:r}=this._handlePartialConnect(t);r=this.expandGroupedGAgentConnection(r);const i={type:"connect",agentIDs:(r=this.expandVirtualSourceAgents(r)).map(t=>t.id),label:this.textFormatter(this.applyLabelPattern(e)),options:n};this.addParallelStages(this._makeConnectParallelStages(s,i))}handleConnectDelayBegin({agent:t,tag:e,options:n,ln:s}){const r=this.currentSection.delayedConnections;if(r.has(e))throw new Error('Duplicate delayed connection "'+e+'"');const{flags:i,gAgents:a}=this._handlePartialConnect([t]),o=this.nextVirtualAgentName(),h={type:"connect-delay-begin",tag:o,agentIDs:null,label:null,options:n};r.set(e,{tag:e,uniqueTag:o,ln:s,gAgents:a,connectStage:h}),this.addParallelStages(this._makeConnectParallelStages(i,h))}handleConnectDelayEnd({agent:t,tag:e,label:n,options:s}){const r=this.currentSection.delayedConnections,i=r.get(e);if(!i)throw new Error('Unknown delayed connection "'+e+'"');let{flags:a,gAgents:o}=this._handlePartialConnect([t]);o=this.expandGroupedGAgentConnection([...i.gAgents,...o]),o=this.expandVirtualSourceAgents(o);let h=i.connectStage.options;if(h.line!==s.line)throw new Error("Mismatched delayed connection arrows");s.right&&(h=Object.assign({},h,{right:s.right})),Object.assign(i.connectStage,{agentIDs:o.map(t=>t.id),label:this.textFormatter(this.applyLabelPattern(n)),options:h});const l={type:"connect-delay-end",tag:i.uniqueTag};this.addParallelStages(this._makeConnectParallelStages(a,l)),r.delete(e)}handleNote({type:t,agents:e,mode:n,label:s}){let r=null;r=0===e.length?vt[t]||[]:e.map(this.toGAgent),this.validateGAgents(r,{allowGrouped:!0});const i=(r=a(r,this.expandGroupedGAgent)).map(t=>t.id),o=new Set(i).size;if("note between"===t&&o<2)throw new Error("note between requires at least 2 agents");this.addStage(this.setGAgentVis(r,!0,"box")),this.defineGAgents(r),this.addStage({type:t,agentIDs:i,mode:n,label:this.textFormatter(s)})}handleAgentDefine({agents:t}){const n=t.map(this.toGAgent);this.validateGAgents(n,{allowGrouped:!0,allowCovered:!0}),e(this.gAgents,n,wt.equals)}handleAgentOptions({agent:t,options:n}){const s=this.toGAgent(t),r=[s];this.validateGAgents(r,{allowGrouped:!0,allowCovered:!0}),e(this.gAgents,r,wt.equals),this.gAgents.filter(({id:t})=>t===s.id).forEach(t=>{e(t.options,n)})}handleAgentBegin({agents:t,mode:e}){const n=t.map(this.toGAgent);this.validateGAgents(n),this.addStage(this.setGAgentVis(n,!0,e,!0))}handleAgentEnd({agents:t,mode:e}){const n=t.filter(t=>this.activeGroups.has(t.name)),s=t.filter(t=>!this.activeGroups.has(t.name)).map(this.toGAgent);this.validateGAgents(s),this.addParallelStages([this.setGAgentHighlight(s,!1),this.setGAgentVis(s,!1,e,!0),...n.map(this.endGroup)])}handleStage(t){this.latestLine=t.ln;try{const e=this.stageHandlers[t.type];if(!e)throw new Error("Unknown command: "+t.type);e(t)}catch(e){if("object"==typeof e&&e.message)throw e.message+=" at line "+(t.ln+1),e}}_reset(){this.agentStates.clear(),this.markers.clear(),this.agentAliases.clear(),this.activeGroups.clear(),this.gAgents.length=0,this.nextID=0,this.nesting.length=0,this.labelPattern=[{token:"label"}]}_finalise(t){p(this.gAgents,this.currentNest.leftGAgent,this.currentNest.rightGAgent),function(t){let e=[],n=new Set;for(let s=0;s{t.formattedLabel=this.textFormatter(t.id)})}generate({stages:t,meta:e={}}){this._reset(),this.textFormatter=e.textFormatter;const n=this.beginNested("global",{tag:"",label:"",name:"",ln:0});if(t.forEach(this.handleStage),1!==this.nesting.length)throw new Error("Unterminated section at line "+(this.currentSection.header.ln+1));if(this.activeGroups.size>0)throw new Error("Unterminated group");this._checkSectionEnd();const s=e.terminators||"none";return this.addParallelStages([this.setGAgentHighlight(this.gAgents,!1),this.setGAgentVis(this.gAgents,!1,s)]),this._finalise(n),function(t,e){for(let n=0;n({})},line:{labelAttrs:Et,padding:{top:2,left:5,right:5,bottom:2},extend:8,margin:0,render:this.renderLineDivider.bind(this,{lineAttrs:{stroke:"#000000"}})},delay:{labelAttrs:Et,padding:{top:2,left:5,right:5,bottom:2},extend:0,margin:0,render:this.renderDelayDivider.bind(this,{dotSize:2,gapSize:2})},tear:{labelAttrs:Et,padding:{top:2,left:5,right:5,bottom:2},extend:8,margin:8,render:this.renderTearDivider.bind(this,{fadeBegin:4,fadeSize:4,zigWidth:4,zigHeight:1,lineAttrs:{stroke:"#000000"}})}}})}}class It{constructor(){this.name="monospace"}build(t){return new Gt(t)}}const Dt={type:"error line-error",suggest:!1,then:{"":0}},Lt=["database","red"],Nt=(()=>{function t(t,e=1){return{type:"variable",suggest:{known:"Agent"},then:Object.assign({},t,{"":0,",":{type:"operator",then:{"":e}}})}}function e(t){return{type:"keyword",suggest:[t+" of ",t+": "],then:{of:{type:"keyword",then:{"":h}},":":{type:"operator",then:{"":i}},"":h}}}function n({exit:t,sourceExit:e,blankExit:n}){const s={type:"operator",then:{"+":Dt,"-":Dt,"*":Dt,"!":Dt,"":t}};return{"+":{type:"operator",then:{"+":Dt,"-":Dt,"*":s,"!":Dt,"":t}},"-":{type:"operator",then:{"+":Dt,"-":Dt,"*":s,"!":{type:"operator",then:{"+":Dt,"-":Dt,"*":Dt,"!":Dt,"":t}},"":t}},"*":{type:"operator",then:Object.assign({"+":s,"-":s,"*":Dt,"!":Dt,"":t},e||t)},"!":s,"":n||t}}const s={type:"",suggest:"\n",then:{}},r={type:"",suggest:!1,then:{}},i=f({"\n":s}),a={type:"operator",then:{"":i,"\n":r}},o=t({"\n":s,as:{type:"keyword",then:{"":{type:"variable",suggest:{known:"Agent"},then:{"":0,",":{type:"operator",then:{"":3}},"\n":s}}}}}),h=t({":":a}),l={type:"variable",suggest:{known:"Agent"},then:{"":0,":":{type:"operator",then:{"":i,"\n":r}},"\n":s}},d={":":{type:"operator",then:{"":f({as:{type:"keyword",then:{"":{type:"variable",suggest:{known:"Agent"},then:{"":0,"\n":s}}}}})}}},g={type:"keyword",then:Object.assign({over:{type:"keyword",then:{"":t(d)}}},d)},c={"\n":s,":":{type:"operator",then:{"":i,"\n":r}},with:{type:"keyword",suggest:["with height "],then:{height:{type:"keyword",then:{"":{type:"number",suggest:["6 ","30 "],then:{"\n":s,":":{type:"operator",then:{"":i,"\n":r}}}}}}}}},u=function(t,e,n){const s=Object.assign({},n);return e.forEach(e=>{s[e]={type:t,then:n}}),s}("keyword",["a","an"],function(t,e,n){const s={},r=Object.assign({},n);return e.forEach(e=>{s[e]={type:t,then:r},r[e]=0}),s}("keyword",Lt,{"\n":s})),p={type:"keyword",then:{"":i,":":{type:"operator",then:{"":i}},"\n":s}},m={title:{type:"keyword",then:{"":i}},theme:{type:"keyword",then:{"":{type:"string",suggest:{global:"themes",suffix:"\n"},then:{"":0,"\n":s}}}},headers:{type:"keyword",then:{none:{type:"keyword",then:{}},cross:{type:"keyword",then:{}},box:{type:"keyword",then:{}},fade:{type:"keyword",then:{}},bar:{type:"keyword",then:{}}}},terminators:{type:"keyword",then:{none:{type:"keyword",then:{}},cross:{type:"keyword",then:{}},box:{type:"keyword",then:{}},fade:{type:"keyword",then:{}},bar:{type:"keyword",then:{}}}},divider:{type:"keyword",then:Object.assign({line:{type:"keyword",then:c},space:{type:"keyword",then:c},delay:{type:"keyword",then:c},tear:{type:"keyword",then:c}},c)},define:{type:"keyword",then:{"":o,as:Dt}},begin:{type:"keyword",then:{"":o,reference:g,as:Dt}},end:{type:"keyword",then:{"":o,as:Dt,"\n":s}},if:p,else:{type:"keyword",suggest:["else\n","else if: "],then:{if:{type:"keyword",suggest:"if: ",then:{"":i,":":{type:"operator",then:{"":i}}}},"\n":s}},repeat:p,group:p,note:{type:"keyword",then:{over:{type:"keyword",then:{"":h}},left:e("left"),right:e("right"),between:{type:"keyword",then:{"":t({":":Dt},h)}}}},state:{type:"keyword",suggest:"state over ",then:{over:{type:"keyword",then:{"":{type:"variable",suggest:{known:"Agent"},then:{"":0,",":Dt,":":a}}}}}},text:{type:"keyword",then:{left:e("left"),right:e("right")}},autolabel:{type:"keyword",then:{off:{type:"keyword",then:{}},"":f({"\n":s},[{v:" diff --git a/scripts/core/ArrayUtilities.js b/scripts/core/ArrayUtilities.mjs similarity index 100% rename from scripts/core/ArrayUtilities.js rename to scripts/core/ArrayUtilities.mjs diff --git a/scripts/core/ArrayUtilities_spec.js b/scripts/core/ArrayUtilities_spec.mjs similarity index 99% rename from scripts/core/ArrayUtilities_spec.js rename to scripts/core/ArrayUtilities_spec.mjs index 7f5bffe..6df91c3 100644 --- a/scripts/core/ArrayUtilities_spec.js +++ b/scripts/core/ArrayUtilities_spec.mjs @@ -7,7 +7,7 @@ import { mergeSets, remove, removeAll, -} from './ArrayUtilities.js'; +} from './ArrayUtilities.mjs'; describe('ArrayUtilities', () => { function ignoreCase(a, b) { diff --git a/scripts/core/DOMWrapper.js b/scripts/core/DOMWrapper.mjs similarity index 100% rename from scripts/core/DOMWrapper.js rename to scripts/core/DOMWrapper.mjs diff --git a/scripts/core/EventObject.js b/scripts/core/EventObject.mjs similarity index 100% rename from scripts/core/EventObject.js rename to scripts/core/EventObject.mjs diff --git a/scripts/core/EventObject_spec.js b/scripts/core/EventObject_spec.mjs similarity index 98% rename from scripts/core/EventObject_spec.js rename to scripts/core/EventObject_spec.mjs index 8039d6c..6cf1fca 100644 --- a/scripts/core/EventObject_spec.js +++ b/scripts/core/EventObject_spec.mjs @@ -1,4 +1,4 @@ -import EventObject from './EventObject.js'; +import EventObject from './EventObject.mjs'; describe('EventObject', () => { let o = null; diff --git a/scripts/core/Random.js b/scripts/core/Random.mjs similarity index 100% rename from scripts/core/Random.js rename to scripts/core/Random.mjs diff --git a/scripts/core/Random_spec.js b/scripts/core/Random_spec.mjs similarity index 96% rename from scripts/core/Random_spec.js rename to scripts/core/Random_spec.mjs index 37a2c23..33fcda3 100644 --- a/scripts/core/Random_spec.js +++ b/scripts/core/Random_spec.mjs @@ -1,4 +1,4 @@ -import Random from './Random.js'; +import Random from './Random.mjs'; describe('Random', () => { let random = null; diff --git a/scripts/core/browser.mjs b/scripts/core/browser.mjs new file mode 100644 index 0000000..30a821b --- /dev/null +++ b/scripts/core/browser.mjs @@ -0,0 +1,15 @@ +export const nodejs = (typeof window === 'undefined'); + +export const headless = nodejs; + +// Thanks, https://stackoverflow.com/a/23522755/1180785 +export const safari = ( + !nodejs && + (/^((?!chrome|android).)*safari/i).test(window.navigator.userAgent) +); + +// Thanks, https://stackoverflow.com/a/9851769/1180785 +export const firefox = ( + !nodejs && + typeof window.InstallTrigger !== 'undefined' +); diff --git a/scripts/core/documents/VirtualDocument.js b/scripts/core/documents/VirtualDocument.mjs similarity index 84% rename from scripts/core/documents/VirtualDocument.js rename to scripts/core/documents/VirtualDocument.mjs index 45026e1..480cb1c 100644 --- a/scripts/core/documents/VirtualDocument.js +++ b/scripts/core/documents/VirtualDocument.mjs @@ -104,6 +104,39 @@ class ElementNode { return false; } + getElementsByTagName(tag) { + const result = []; + this.traverseDescendants((o) => { + if(o.tagName === tag) { + result.push(o); + } + }); + return result; + } + + getElementsByClassName(className) { + const result = []; + const check = ' ' + className + ' '; + this.traverseDescendants((o) => { + const cls = ' ' + (o.getAttribute('class') || '') + ' '; + if(cls.indexOf(check) !== -1) { + result.push(o); + } + }); + return result; + } + + traverseDescendants(fn) { + if(fn(this) === false) { + return; + } + for(const child of this.childNodes) { + if(child.traverseDescendants) { + child.traverseDescendants(fn); + } + } + } + get firstChild() { return this.childNodes[0] || null; } @@ -195,7 +228,7 @@ class ElementNode { } } -export default class VirtualDocument { +export class VirtualDocument { createElement(tag) { return new ElementNode(this, tag, ''); } @@ -208,3 +241,9 @@ export default class VirtualDocument { return new TextNode(content); } } + +export class Event { + constructor(type) { + this.type = type; + } +} diff --git a/scripts/core/documents/VirtualDocument_spec.js b/scripts/core/documents/VirtualDocument_spec.mjs similarity index 99% rename from scripts/core/documents/VirtualDocument_spec.js rename to scripts/core/documents/VirtualDocument_spec.mjs index a9919a9..9d6940b 100644 --- a/scripts/core/documents/VirtualDocument_spec.js +++ b/scripts/core/documents/VirtualDocument_spec.mjs @@ -1,4 +1,4 @@ -import VirtualDocument from './VirtualDocument.js'; +import {Event, VirtualDocument} from './VirtualDocument.mjs'; describe('VirtualDocument', () => { const doc = new VirtualDocument(); diff --git a/scripts/image/Blur.js b/scripts/image/Blur.mjs similarity index 96% rename from scripts/image/Blur.js rename to scripts/image/Blur.mjs index dc067bf..f40fd1d 100644 --- a/scripts/image/Blur.js +++ b/scripts/image/Blur.mjs @@ -1,4 +1,4 @@ -import ImageRegion from './ImageRegion.js'; +import ImageRegion from './ImageRegion.mjs'; export function makeGaussianKernel(size) { const sz = Math.ceil(size * 3); diff --git a/scripts/image/Blur_spec.js b/scripts/image/Blur_spec.mjs similarity index 96% rename from scripts/image/Blur_spec.js rename to scripts/image/Blur_spec.mjs index 72cdff6..1b5d262 100644 --- a/scripts/image/Blur_spec.js +++ b/scripts/image/Blur_spec.mjs @@ -1,5 +1,5 @@ -import ImageRegion from './ImageRegion.js'; -import {blur2D} from './Blur.js'; +import ImageRegion from './ImageRegion.mjs'; +import {blur2D} from './Blur.mjs'; describe('Blur', () => { const PRECISION = 0.01; diff --git a/scripts/image/Composition.js b/scripts/image/Composition.mjs similarity index 95% rename from scripts/image/Composition.js rename to scripts/image/Composition.mjs index 87d3dab..c35c643 100644 --- a/scripts/image/Composition.js +++ b/scripts/image/Composition.mjs @@ -1,4 +1,4 @@ -import ImageRegion from './ImageRegion.js'; +import ImageRegion from './ImageRegion.mjs'; function compose(input1, input2, fn, {target = null} = {}) { input1.checkCompatible(input2); diff --git a/scripts/image/Composition_spec.js b/scripts/image/Composition_spec.mjs similarity index 95% rename from scripts/image/Composition_spec.js rename to scripts/image/Composition_spec.mjs index e5bb9aa..202aaf4 100644 --- a/scripts/image/Composition_spec.js +++ b/scripts/image/Composition_spec.mjs @@ -1,5 +1,5 @@ -import ImageRegion from './ImageRegion.js'; -import {subtract} from './Composition.js'; +import ImageRegion from './ImageRegion.mjs'; +import {subtract} from './Composition.mjs'; describe('Composition', () => { let inputA = null; diff --git a/scripts/image/ImageRegion.js b/scripts/image/ImageRegion.mjs similarity index 100% rename from scripts/image/ImageRegion.js rename to scripts/image/ImageRegion.mjs diff --git a/scripts/image/ImageRegion_spec.js b/scripts/image/ImageRegion_spec.mjs similarity index 97% rename from scripts/image/ImageRegion_spec.js rename to scripts/image/ImageRegion_spec.mjs index 7482f58..a62d93c 100644 --- a/scripts/image/ImageRegion_spec.js +++ b/scripts/image/ImageRegion_spec.mjs @@ -1,9 +1,13 @@ /* eslint-disable max-statements */ -import ImageRegion from './ImageRegion.js'; +import ImageRegion from './ImageRegion.mjs'; +import {nodejs} from '../core/browser.mjs'; describe('ImageRegion', () => { function makeCanvas(w, h) { + if(nodejs) { + return pending('No canvas support in NodeJS'); + } const canvas = document.createElement('canvas'); canvas.width = w; canvas.height = h; diff --git a/scripts/image/ImageSimilarity.js b/scripts/image/ImageSimilarity.mjs similarity index 74% rename from scripts/image/ImageSimilarity.js rename to scripts/image/ImageSimilarity.mjs index 3215edc..40ff3f0 100644 --- a/scripts/image/ImageSimilarity.js +++ b/scripts/image/ImageSimilarity.mjs @@ -1,6 +1,7 @@ -import './Blur.js'; -import './Composition.js'; -import ImageRegion from './ImageRegion.js'; +import './Blur.mjs'; +import './Composition.mjs'; +import ImageRegion from './ImageRegion.mjs'; +import {headless} from '../core/browser.mjs'; function getThresholds({ pixelThresh = 2, @@ -74,20 +75,22 @@ export const matchers = { return {pass: true}; } - document.body.appendChild(makeImageComparison( - actual, - expected, - options, - 'Image comparison (expected similar)' - )); - - const details = (options.details ? + let message = 'Expected images to be similar'; + if(!headless) { + document.body.appendChild(makeImageComparison( + actual, + expected, + options, + 'Image comparison (expected similar)' + )); + message += ' (see below for comparison)'; + } + message += '.' + (options.details ? ' Details: ' + options.details : '' ); return { - message: 'Expected images to be similar ' + - '(see below for comparison).' + details, + message, pass: false, }; }, @@ -97,20 +100,22 @@ export const matchers = { return {pass: true}; } - document.body.appendChild(makeImageComparison( - actual, - expected, - options, - 'Image comparison (expected different)' - )); - - const details = (options.details ? + let message = 'Expected images to differ'; + if(!headless) { + document.body.appendChild(makeImageComparison( + actual, + expected, + options, + 'Image comparison (expected different)' + )); + message += ' (see below for comparison)'; + } + message += '.' + (options.details ? ' Details: ' + options.details : '' ); return { - message: 'Expected images to differ ' + - '(see below for comparison).' + details, + message, pass: false, }; }, diff --git a/scripts/image/ImageSimilarity_spec.js b/scripts/image/ImageSimilarity_spec.mjs similarity index 92% rename from scripts/image/ImageSimilarity_spec.js rename to scripts/image/ImageSimilarity_spec.mjs index 424cab6..dd1b10b 100644 --- a/scripts/image/ImageSimilarity_spec.js +++ b/scripts/image/ImageSimilarity_spec.mjs @@ -1,5 +1,5 @@ -import {isSimilar, matchers} from './ImageSimilarity.js'; -import ImageRegion from './ImageRegion.js'; +import {isSimilar, matchers} from './ImageSimilarity.mjs'; +import ImageRegion from './ImageRegion.mjs'; describe('ImageSimilarity', () => { let inputA = null; diff --git a/scripts/interface/ComponentsLibrary_spec.js b/scripts/interface/ComponentsLibrary_spec.js deleted file mode 100644 index adf6839..0000000 --- a/scripts/interface/ComponentsLibrary_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import ComponentsLibrary from './ComponentsLibrary.js'; -import SequenceDiagram from '../sequence/SequenceDiagram.js'; - -const themes = new SequenceDiagram().getThemeNames().slice(1); - -function checkSample(src) { - it('renders without error', () => { - expect(() => new SequenceDiagram(src)).not.toThrow(); - }); - - themes.forEach((themeName) => { - it('renders without error in ' + themeName + ' theme', () => { - expect(() => new SequenceDiagram( - 'theme ' + themeName + '\n' + src - )).not.toThrow(); - }); - }); -} - -describe('Components Library', () => { - ComponentsLibrary.forEach(({title, code, preview}) => { - describe(title, () => { - checkSample(preview || code); - }); - }); -}); diff --git a/scripts/jasmineConfig.js b/scripts/jasmineConfig.js deleted file mode 100644 index e10bd30..0000000 --- a/scripts/jasmineConfig.js +++ /dev/null @@ -1,31 +0,0 @@ -// Jasmine test configuration. -// See specs.js for the list of spec files - -jasmine.executeAllTests = window.onload; -window.onload = null; - -const matchers = { - toBeNear: () => ({ - compare: (actual, expected, range) => { - if( - typeof expected !== 'number' || - typeof range !== 'number' || - range < 0 - ) { - throw new Error( - 'Invalid toBeNear(' + expected + ',' + range + ')' - ); - } - if(typeof actual !== 'number') { - throw new Error('Expected a number, got ' + actual); - } - return { - pass: Math.abs(actual - expected) <= range, - }; - }, - }), -}; - -beforeAll(() => { - jasmine.addMatchers(matchers); -}); diff --git a/scripts/rollup.config.js b/scripts/rollup.config.js new file mode 100644 index 0000000..f1c4cdd --- /dev/null +++ b/scripts/rollup.config.js @@ -0,0 +1,10 @@ +export default [ + { + input: 'scripts/standalone.mjs', + output: { + file: 'lib/sequence-diagram.js', + format: 'iife', + name: 'SequenceDiagram', + }, + }, +]; diff --git a/scripts/sequence/CodeMirrorHints.js b/scripts/sequence/CodeMirrorHints.mjs similarity index 98% rename from scripts/sequence/CodeMirrorHints.js rename to scripts/sequence/CodeMirrorHints.mjs index dab2820..6a17d15 100644 --- a/scripts/sequence/CodeMirrorHints.js +++ b/scripts/sequence/CodeMirrorHints.mjs @@ -1,6 +1,6 @@ /* eslint-disable complexity */ // Temporary ignore while switching linter -import {last, mergeSets} from '../core/ArrayUtilities.js'; +import {last, mergeSets} from '../core/ArrayUtilities.mjs'; const TRIMMER = /^([ \t]*)(.*)$/; const SQUASH = { diff --git a/scripts/sequence/CodeMirrorMode.js b/scripts/sequence/CodeMirrorMode.mjs similarity index 99% rename from scripts/sequence/CodeMirrorMode.js rename to scripts/sequence/CodeMirrorMode.mjs index 6326816..c6972c9 100644 --- a/scripts/sequence/CodeMirrorMode.js +++ b/scripts/sequence/CodeMirrorMode.mjs @@ -2,7 +2,7 @@ /* eslint-disable sort-keys */ // Maybe later /* eslint-disable complexity */ // Temporary ignore while switching linter -import {flatMap, last, mergeSets} from '../core/ArrayUtilities.js'; +import {flatMap, last, mergeSets} from '../core/ArrayUtilities.mjs'; const CM_ERROR = {type: 'error line-error', suggest: false, then: {'': 0}}; diff --git a/scripts/sequence/CodeMirrorMode_spec.js b/scripts/sequence/CodeMirrorMode_webspec.mjs similarity index 99% rename from scripts/sequence/CodeMirrorMode_spec.js rename to scripts/sequence/CodeMirrorMode_webspec.mjs index 3acc7e1..47b2b75 100644 --- a/scripts/sequence/CodeMirrorMode_spec.js +++ b/scripts/sequence/CodeMirrorMode_webspec.mjs @@ -2,7 +2,7 @@ /* eslint-disable max-statements */ /* eslint-disable sort-keys */ // Maybe later -import SequenceDiagram from './SequenceDiagram.js'; +import SequenceDiagram from './SequenceDiagram.mjs'; const CM = window.CodeMirror; diff --git a/scripts/sequence/Exporter.js b/scripts/sequence/Exporter.mjs similarity index 95% rename from scripts/sequence/Exporter.js rename to scripts/sequence/Exporter.mjs index 606c152..8b46bdd 100644 --- a/scripts/sequence/Exporter.js +++ b/scripts/sequence/Exporter.mjs @@ -1,5 +1,4 @@ -// Thanks, https://stackoverflow.com/a/23522755/1180785 -const safari = (/^((?!chrome|android).)*safari/i).test(navigator.userAgent); +import {safari} from '../core/browser.mjs'; export default class Exporter { constructor() { diff --git a/scripts/sequence/Generator.js b/scripts/sequence/Generator.mjs similarity index 99% rename from scripts/sequence/Generator.js rename to scripts/sequence/Generator.mjs index d7f9102..9696c2c 100644 --- a/scripts/sequence/Generator.js +++ b/scripts/sequence/Generator.mjs @@ -10,7 +10,7 @@ import { mergeSets, remove, removeAll, -} from '../core/ArrayUtilities.js'; +} from '../core/ArrayUtilities.mjs'; class AgentState { constructor({ diff --git a/scripts/sequence/Generator_spec.js b/scripts/sequence/Generator_spec.mjs similarity index 99% rename from scripts/sequence/Generator_spec.js rename to scripts/sequence/Generator_spec.mjs index 3cfe296..6e69c91 100644 --- a/scripts/sequence/Generator_spec.js +++ b/scripts/sequence/Generator_spec.mjs @@ -2,7 +2,7 @@ /* eslint-disable max-statements */ /* eslint-disable sort-keys */ // Maybe later -import Generator from './Generator.js'; +import Generator from './Generator.mjs'; describe('Sequence Generator', () => { const generator = new Generator(); diff --git a/scripts/sequence/LabelPatternParser.js b/scripts/sequence/LabelPatternParser.mjs similarity index 100% rename from scripts/sequence/LabelPatternParser.js rename to scripts/sequence/LabelPatternParser.mjs diff --git a/scripts/sequence/LabelPatternParser_spec.js b/scripts/sequence/LabelPatternParser_spec.mjs similarity index 97% rename from scripts/sequence/LabelPatternParser_spec.js rename to scripts/sequence/LabelPatternParser_spec.mjs index 7236d01..51d2d73 100644 --- a/scripts/sequence/LabelPatternParser_spec.js +++ b/scripts/sequence/LabelPatternParser_spec.mjs @@ -1,4 +1,4 @@ -import parser from './LabelPatternParser.js'; +import parser from './LabelPatternParser.mjs'; describe('Label Pattern Parser', () => { it('converts simple text', () => { diff --git a/scripts/sequence/MarkdownParser.js b/scripts/sequence/MarkdownParser.mjs similarity index 100% rename from scripts/sequence/MarkdownParser.js rename to scripts/sequence/MarkdownParser.mjs diff --git a/scripts/sequence/MarkdownParser_spec.js b/scripts/sequence/MarkdownParser_spec.mjs similarity index 96% rename from scripts/sequence/MarkdownParser_spec.js rename to scripts/sequence/MarkdownParser_spec.mjs index d10d8ab..2bf9cda 100644 --- a/scripts/sequence/MarkdownParser_spec.js +++ b/scripts/sequence/MarkdownParser_spec.mjs @@ -1,8 +1,8 @@ /* eslint-disable sort-keys */ // Maybe later -import {dom, textSizerFactory} from '../stubs/TestDOM.js'; -import SVG from '../svg/SVG.js'; -import parser from './MarkdownParser.js'; +import {dom, textSizerFactory} from '../../spec/stubs/TestDOM.mjs'; +import SVG from '../svg/SVG.mjs'; +import parser from './MarkdownParser.mjs'; describe('Markdown Parser', () => { it('converts simple text', () => { diff --git a/scripts/sequence/Parser.js b/scripts/sequence/Parser.mjs similarity index 98% rename from scripts/sequence/Parser.js rename to scripts/sequence/Parser.mjs index e443026..28bb511 100644 --- a/scripts/sequence/Parser.js +++ b/scripts/sequence/Parser.mjs @@ -3,10 +3,10 @@ /* eslint-disable complexity */ // Temporary ignore while switching linter /* eslint-disable no-param-reassign */ // Also temporary -import {combine, last} from '../core/ArrayUtilities.js'; -import Tokeniser from './Tokeniser.js'; -import labelPatternParser from './LabelPatternParser.js'; -import markdownParser from './MarkdownParser.js'; +import {combine, last} from '../core/ArrayUtilities.mjs'; +import Tokeniser from './Tokeniser.mjs'; +import labelPatternParser from './LabelPatternParser.mjs'; +import markdownParser from './MarkdownParser.mjs'; const BLOCK_TYPES = { 'if': { diff --git a/scripts/sequence/Parser_spec.js b/scripts/sequence/Parser_spec.mjs similarity index 99% rename from scripts/sequence/Parser_spec.js rename to scripts/sequence/Parser_spec.mjs index 581867c..829d2d6 100644 --- a/scripts/sequence/Parser_spec.js +++ b/scripts/sequence/Parser_spec.mjs @@ -2,7 +2,7 @@ /* eslint-disable max-statements */ /* eslint-disable sort-keys */ // Maybe later -import Parser from './Parser.js'; +import Parser from './Parser.mjs'; describe('Sequence Parser', () => { const parser = new Parser(); diff --git a/scripts/sequence/Readme_spec.js b/scripts/sequence/Readme_webspec.mjs similarity index 90% rename from scripts/sequence/Readme_spec.js rename to scripts/sequence/Readme_webspec.mjs index 025bf98..c7009f9 100644 --- a/scripts/sequence/Readme_spec.js +++ b/scripts/sequence/Readme_webspec.mjs @@ -1,6 +1,6 @@ -import ImageRegion from '../image/ImageRegion.js'; -import SequenceDiagram from './SequenceDiagram.js'; -import {matchers} from '../image/ImageSimilarity.js'; +import ImageRegion from '../image/ImageRegion.mjs'; +import SequenceDiagram from './SequenceDiagram.mjs'; +import {matchers} from '../image/ImageSimilarity.mjs'; const RESOLUTION = 4; diff --git a/scripts/sequence/Renderer.js b/scripts/sequence/Renderer.mjs similarity index 97% rename from scripts/sequence/Renderer.js rename to scripts/sequence/Renderer.mjs index 21e6fdb..0697e6b 100644 --- a/scripts/sequence/Renderer.js +++ b/scripts/sequence/Renderer.mjs @@ -1,21 +1,21 @@ /* eslint-disable max-lines */ -import './components/AgentCap.js'; -import './components/AgentHighlight.js'; -import './components/Block.js'; -import './components/Connect.js'; -import './components/Divider.js'; -import './components/Marker.js'; -import './components/Note.js'; -import './components/Parallel.js'; +import './components/AgentCap.mjs'; +import './components/AgentHighlight.mjs'; +import './components/Block.mjs'; +import './components/Connect.mjs'; +import './components/Divider.mjs'; +import './components/Marker.mjs'; +import './components/Note.mjs'; +import './components/Parallel.mjs'; import { cleanRenderPreResult, getComponents, -} from './components/BaseComponent.js'; -import DOMWrapper from '../core/DOMWrapper.js'; -import EventObject from '../core/EventObject.js'; -import SVG from '../svg/SVG.js'; -import {mergeSets} from '../core/ArrayUtilities.js'; +} from './components/BaseComponent.mjs'; +import DOMWrapper from '../core/DOMWrapper.mjs'; +import EventObject from '../core/EventObject.mjs'; +import SVG from '../svg/SVG.mjs'; +import {mergeSets} from '../core/ArrayUtilities.mjs'; function findExtremes(agentInfos, agentIDs) { let min = null; diff --git a/scripts/sequence/Renderer_spec.js b/scripts/sequence/Renderer_spec.mjs similarity index 96% rename from scripts/sequence/Renderer_spec.js rename to scripts/sequence/Renderer_spec.mjs index 89f7758..d040f3c 100644 --- a/scripts/sequence/Renderer_spec.js +++ b/scripts/sequence/Renderer_spec.mjs @@ -1,21 +1,18 @@ /* eslint-disable sort-keys */ // Maybe later -import {Factory as BasicThemeFactory} from './themes/Basic.js'; -import Renderer from './Renderer.js'; +import {VirtualDocument, textSizerFactory} from '../../spec/stubs/TestDOM.mjs'; +import {Factory as BasicThemeFactory} from './themes/Basic.mjs'; +import Renderer from './Renderer.mjs'; describe('Sequence Renderer', () => { let renderer = null; beforeEach(() => { renderer = new Renderer({ + document: new VirtualDocument(), themes: [new BasicThemeFactory()], - document: window.document, + textSizerFactory, }); - document.body.appendChild(renderer.dom()); - }); - - afterEach(() => { - document.body.removeChild(renderer.dom()); }); describe('.dom', () => { diff --git a/scripts/sequence/SequenceDiagram.js b/scripts/sequence/SequenceDiagram.mjs similarity index 93% rename from scripts/sequence/SequenceDiagram.js rename to scripts/sequence/SequenceDiagram.mjs index 098c42b..e1ecf30 100644 --- a/scripts/sequence/SequenceDiagram.js +++ b/scripts/sequence/SequenceDiagram.mjs @@ -1,13 +1,13 @@ -import {Factory as BasicThemeFactory} from './themes/Basic.js'; -import {Factory as ChunkyThemeFactory} from './themes/Chunky.js'; -import EventObject from '../core/EventObject.js'; -import Exporter from './Exporter.js'; -import Generator from './Generator.js'; -import {Factory as MonospaceThemeFactory} from './themes/Monospace.js'; -import Parser from './Parser.js'; -import Renderer from './Renderer.js'; -import {Factory as SketchThemeFactory} from './themes/Sketch.js'; -import {getHints} from './CodeMirrorHints.js'; +import {Factory as BasicThemeFactory} from './themes/Basic.mjs'; +import {Factory as ChunkyThemeFactory} from './themes/Chunky.mjs'; +import EventObject from '../core/EventObject.mjs'; +import Exporter from './Exporter.mjs'; +import Generator from './Generator.mjs'; +import {Factory as MonospaceThemeFactory} from './themes/Monospace.mjs'; +import Parser from './Parser.mjs'; +import Renderer from './Renderer.mjs'; +import {Factory as SketchThemeFactory} from './themes/Sketch.mjs'; +import {getHints} from './CodeMirrorHints.mjs'; const themes = [ new BasicThemeFactory(), @@ -65,7 +65,9 @@ function renderAll(diagrams) { function pickDocument(container) { if(container) { - return container.ownerDocument; + return container.ownerDocument || null; + } else if(typeof window === 'undefined') { + return null; } else { return window.document; } @@ -442,6 +444,10 @@ function convertAll(root = null, className = 'sequence-diagram') { convert(els); } +function getDefaultThemeNames() { + return themes.map((theme) => theme.name); +} + Object.assign(SequenceDiagram, { Exporter, Generator, @@ -451,6 +457,7 @@ Object.assign(SequenceDiagram, { convert, convertAll, extractCodeFromSVG, + getDefaultThemeNames, registerCodeMirrorMode, renderAll, themes, diff --git a/scripts/sequence/SequenceDiagram_spec.js b/scripts/sequence/SequenceDiagram_spec.mjs similarity index 89% rename from scripts/sequence/SequenceDiagram_spec.js rename to scripts/sequence/SequenceDiagram_spec.mjs index 90f4f90..8a75282 100644 --- a/scripts/sequence/SequenceDiagram_spec.js +++ b/scripts/sequence/SequenceDiagram_spec.mjs @@ -1,9 +1,10 @@ -import Exporter from './Exporter.js'; -import Generator from './Generator.js'; -import Parser from './Parser.js'; -import Renderer from './Renderer.js'; -import SequenceDiagram from './SequenceDiagram.js'; -import {textSizerFactory} from '../stubs/TestDOM.js'; +import {VirtualDocument, textSizerFactory} from '../../spec/stubs/TestDOM.mjs'; +import Exporter from './Exporter.mjs'; +import Generator from './Generator.mjs'; +import Parser from './Parser.mjs'; +import Renderer from './Renderer.mjs'; +import SequenceDiagram from './SequenceDiagram.mjs'; +import {nodejs} from '../core/browser.mjs'; describe('SequenceDiagram', () => { function getSimplifiedContent(d) { @@ -18,6 +19,7 @@ describe('SequenceDiagram', () => { beforeEach(() => { diagram = new SequenceDiagram({ + document: new VirtualDocument(), namespace: '', textSizerFactory, }); @@ -167,6 +169,11 @@ describe('SequenceDiagram', () => { }); it('measures OS fonts correctly on the first render', (done) => { + if(nodejs) { + pending('NodeJS font rendering not implemented yet'); + return; + } + const code = 'title message'; const sd = new SequenceDiagram(code); const widthImmediate = sd.getSize().width; @@ -187,6 +194,11 @@ describe('SequenceDiagram', () => { }); it('measures embedded fonts correctly on the first render', (done) => { + if(nodejs) { + pending('NodeJS font rendering not implemented yet'); + return; + } + const code = 'theme sketch\ntitle message'; const sd = new SequenceDiagram(code); const widthImmediate = sd.getSize().width; diff --git a/scripts/sequence/SequenceDiagram_visual_spec.js b/scripts/sequence/SequenceDiagram_visual_webspec.mjs similarity index 85% rename from scripts/sequence/SequenceDiagram_visual_spec.js rename to scripts/sequence/SequenceDiagram_visual_webspec.mjs index 09aeee1..2ee2b37 100644 --- a/scripts/sequence/SequenceDiagram_visual_spec.js +++ b/scripts/sequence/SequenceDiagram_visual_webspec.mjs @@ -1,12 +1,12 @@ -import ImageRegion from '../image/ImageRegion.js'; -import SequenceDiagram from './SequenceDiagram.js'; -import TESTS from './test-images/list.js'; -import {matchers} from '../image/ImageSimilarity.js'; +import ImageRegion from '../image/ImageRegion.mjs'; +import SequenceDiagram from './SequenceDiagram.mjs'; +import TESTS from '../../spec/images/list.mjs'; +import {matchers} from '../image/ImageSimilarity.mjs'; describe('SequenceDiagram Visuals', () => { const RESOLUTION = 4; - const IMAGE_BASE_PATH = 'scripts/sequence/test-images/'; + const IMAGE_BASE_PATH = 'spec/images/'; const COLLAPSE_REGEX = new RegExp(/# collapse/g); diff --git a/scripts/sequence/Tokeniser.js b/scripts/sequence/Tokeniser.mjs similarity index 98% rename from scripts/sequence/Tokeniser.js rename to scripts/sequence/Tokeniser.mjs index ea3d1cb..e65513c 100644 --- a/scripts/sequence/Tokeniser.js +++ b/scripts/sequence/Tokeniser.mjs @@ -1,4 +1,4 @@ -import CMMode from './CodeMirrorMode.js'; +import CMMode from './CodeMirrorMode.mjs'; function execAt(str, reg, i) { reg.lastIndex = i; diff --git a/scripts/sequence/Tokeniser_spec.js b/scripts/sequence/Tokeniser_spec.mjs similarity index 99% rename from scripts/sequence/Tokeniser_spec.js rename to scripts/sequence/Tokeniser_spec.mjs index 0ea746e..4dbaaca 100644 --- a/scripts/sequence/Tokeniser_spec.js +++ b/scripts/sequence/Tokeniser_spec.mjs @@ -1,4 +1,4 @@ -import Tokeniser from './Tokeniser.js'; +import Tokeniser from './Tokeniser.mjs'; describe('Sequence Tokeniser', () => { const tokeniser = new Tokeniser(); diff --git a/scripts/sequence/components/AgentCap.js b/scripts/sequence/components/AgentCap.mjs similarity index 98% rename from scripts/sequence/components/AgentCap.js rename to scripts/sequence/components/AgentCap.mjs index b36569e..6c158f5 100644 --- a/scripts/sequence/components/AgentCap.js +++ b/scripts/sequence/components/AgentCap.mjs @@ -1,5 +1,5 @@ -import BaseComponent, {register} from './BaseComponent.js'; -import {mergeSets, removeAll} from '../../core/ArrayUtilities.js'; +import BaseComponent, {register} from './BaseComponent.mjs'; +import {mergeSets, removeAll} from '../../core/ArrayUtilities.mjs'; const OUTLINE_ATTRS = { 'class': 'outline', diff --git a/scripts/sequence/components/AgentCap_spec.js b/scripts/sequence/components/AgentCap_spec.mjs similarity index 75% rename from scripts/sequence/components/AgentCap_spec.js rename to scripts/sequence/components/AgentCap_spec.mjs index 5e42bc1..238e19b 100644 --- a/scripts/sequence/components/AgentCap_spec.js +++ b/scripts/sequence/components/AgentCap_spec.mjs @@ -1,5 +1,5 @@ -import AgentCap from './AgentCap.js'; -import {getComponents} from './BaseComponent.js'; +import AgentCap from './AgentCap.mjs'; +import {getComponents} from './BaseComponent.mjs'; describe('AgentCap', () => { it('registers itself with the component store', () => { diff --git a/scripts/sequence/components/AgentHighlight.js b/scripts/sequence/components/AgentHighlight.mjs similarity index 94% rename from scripts/sequence/components/AgentHighlight.js rename to scripts/sequence/components/AgentHighlight.mjs index d81510a..ef5cf59 100644 --- a/scripts/sequence/components/AgentHighlight.js +++ b/scripts/sequence/components/AgentHighlight.mjs @@ -1,4 +1,4 @@ -import BaseComponent, {register} from './BaseComponent.js'; +import BaseComponent, {register} from './BaseComponent.mjs'; export default class AgentHighlight extends BaseComponent { radius(highlighted, env) { diff --git a/scripts/sequence/components/AgentHighlight_spec.js b/scripts/sequence/components/AgentHighlight_spec.mjs similarity index 93% rename from scripts/sequence/components/AgentHighlight_spec.js rename to scripts/sequence/components/AgentHighlight_spec.mjs index 69dba27..cc9ea9b 100644 --- a/scripts/sequence/components/AgentHighlight_spec.js +++ b/scripts/sequence/components/AgentHighlight_spec.mjs @@ -1,5 +1,5 @@ -import AgentHighlight from './AgentHighlight.js'; -import {getComponents} from './BaseComponent.js'; +import AgentHighlight from './AgentHighlight.mjs'; +import {getComponents} from './BaseComponent.mjs'; describe('AgentHighlight', () => { const highlight = new AgentHighlight(); diff --git a/scripts/sequence/components/BaseComponent.js b/scripts/sequence/components/BaseComponent.mjs similarity index 100% rename from scripts/sequence/components/BaseComponent.js rename to scripts/sequence/components/BaseComponent.mjs diff --git a/scripts/sequence/components/Block.js b/scripts/sequence/components/Block.mjs similarity index 97% rename from scripts/sequence/components/Block.js rename to scripts/sequence/components/Block.mjs index f5697ee..a1795da 100644 --- a/scripts/sequence/components/Block.js +++ b/scripts/sequence/components/Block.mjs @@ -1,5 +1,5 @@ -import BaseComponent, {register} from './BaseComponent.js'; -import {mergeSets, removeAll} from '../../core/ArrayUtilities.js'; +import BaseComponent, {register} from './BaseComponent.mjs'; +import {mergeSets, removeAll} from '../../core/ArrayUtilities.mjs'; const OUTLINE_ATTRS = { 'class': 'outline', diff --git a/scripts/sequence/components/Block_spec.js b/scripts/sequence/components/Block_spec.mjs similarity index 75% rename from scripts/sequence/components/Block_spec.js rename to scripts/sequence/components/Block_spec.mjs index 1f36c95..e556cb7 100644 --- a/scripts/sequence/components/Block_spec.js +++ b/scripts/sequence/components/Block_spec.mjs @@ -1,5 +1,5 @@ -import {BlockBegin, BlockEnd, BlockSplit} from './Block.js'; -import {getComponents} from './BaseComponent.js'; +import {BlockBegin, BlockEnd, BlockSplit} from './Block.mjs'; +import {getComponents} from './BaseComponent.mjs'; describe('Block', () => { it('registers itself with the component store', () => { diff --git a/scripts/sequence/components/Connect.js b/scripts/sequence/components/Connect.mjs similarity index 99% rename from scripts/sequence/components/Connect.js rename to scripts/sequence/components/Connect.mjs index d546f5f..e83e1a3 100644 --- a/scripts/sequence/components/Connect.js +++ b/scripts/sequence/components/Connect.mjs @@ -1,7 +1,7 @@ /* eslint-disable sort-keys */ // Maybe later -import BaseComponent, {register} from './BaseComponent.js'; -import {mergeSets} from '../../core/ArrayUtilities.js'; +import BaseComponent, {register} from './BaseComponent.mjs'; +import {mergeSets} from '../../core/ArrayUtilities.mjs'; const OUTLINE_ATTRS = { 'class': 'outline', diff --git a/scripts/sequence/components/Connect_spec.js b/scripts/sequence/components/Connect_spec.mjs similarity index 87% rename from scripts/sequence/components/Connect_spec.js rename to scripts/sequence/components/Connect_spec.mjs index bf9bfef..96ea19d 100644 --- a/scripts/sequence/components/Connect_spec.js +++ b/scripts/sequence/components/Connect_spec.mjs @@ -1,5 +1,5 @@ -import {Connect, ConnectDelayBegin, ConnectDelayEnd} from './Connect.js'; -import {getComponents} from './BaseComponent.js'; +import {Connect, ConnectDelayBegin, ConnectDelayEnd} from './Connect.mjs'; +import {getComponents} from './BaseComponent.mjs'; describe('Connect', () => { it('registers itself with the component store', () => { diff --git a/scripts/sequence/components/Divider.js b/scripts/sequence/components/Divider.mjs similarity index 97% rename from scripts/sequence/components/Divider.js rename to scripts/sequence/components/Divider.mjs index a745667..f6d9fdb 100644 --- a/scripts/sequence/components/Divider.js +++ b/scripts/sequence/components/Divider.mjs @@ -1,4 +1,4 @@ -import BaseComponent, {register} from './BaseComponent.js'; +import BaseComponent, {register} from './BaseComponent.mjs'; const OUTLINE_ATTRS = { 'class': 'outline', diff --git a/scripts/sequence/components/Divider_spec.js b/scripts/sequence/components/Divider_spec.mjs similarity index 73% rename from scripts/sequence/components/Divider_spec.js rename to scripts/sequence/components/Divider_spec.mjs index ecd9d78..63fcd3d 100644 --- a/scripts/sequence/components/Divider_spec.js +++ b/scripts/sequence/components/Divider_spec.mjs @@ -1,5 +1,5 @@ -import Divider from './Divider.js'; -import {getComponents} from './BaseComponent.js'; +import Divider from './Divider.mjs'; +import {getComponents} from './BaseComponent.mjs'; describe('Divider', () => { describe('Divider', () => { diff --git a/scripts/sequence/components/Marker.js b/scripts/sequence/components/Marker.mjs similarity index 89% rename from scripts/sequence/components/Marker.js rename to scripts/sequence/components/Marker.mjs index d9e8ace..bc073ab 100644 --- a/scripts/sequence/components/Marker.js +++ b/scripts/sequence/components/Marker.mjs @@ -1,4 +1,4 @@ -import BaseComponent, {register} from './BaseComponent.js'; +import BaseComponent, {register} from './BaseComponent.mjs'; export class Mark extends BaseComponent { makeState(state) { diff --git a/scripts/sequence/components/Marker_spec.js b/scripts/sequence/components/Marker_spec.mjs similarity index 93% rename from scripts/sequence/components/Marker_spec.js rename to scripts/sequence/components/Marker_spec.mjs index faedc08..9fa5b0b 100644 --- a/scripts/sequence/components/Marker_spec.js +++ b/scripts/sequence/components/Marker_spec.mjs @@ -1,5 +1,5 @@ -import {Async, Mark} from './Marker.js'; -import {getComponents} from './BaseComponent.js'; +import {Async, Mark} from './Marker.mjs'; +import {getComponents} from './BaseComponent.mjs'; const mark = new Mark(); const async = new Async(); diff --git a/scripts/sequence/components/Note.js b/scripts/sequence/components/Note.mjs similarity index 98% rename from scripts/sequence/components/Note.js rename to scripts/sequence/components/Note.mjs index de6f2b0..5608897 100644 --- a/scripts/sequence/components/Note.js +++ b/scripts/sequence/components/Note.mjs @@ -1,7 +1,7 @@ /* eslint-disable complexity */ // Temporary ignore while switching linter /* eslint-disable no-param-reassign */ // Also temporary -import BaseComponent, {register} from './BaseComponent.js'; +import BaseComponent, {register} from './BaseComponent.mjs'; const OUTLINE_ATTRS = { 'class': 'outline', diff --git a/scripts/sequence/components/Note_spec.js b/scripts/sequence/components/Note_spec.mjs similarity index 86% rename from scripts/sequence/components/Note_spec.js rename to scripts/sequence/components/Note_spec.mjs index 700f637..bc96705 100644 --- a/scripts/sequence/components/Note_spec.js +++ b/scripts/sequence/components/Note_spec.mjs @@ -1,5 +1,5 @@ -import {NoteBetween, NoteOver, NoteSide} from './Note.js'; -import {getComponents} from './BaseComponent.js'; +import {NoteBetween, NoteOver, NoteSide} from './Note.mjs'; +import {getComponents} from './BaseComponent.mjs'; describe('NoteOver', () => { it('registers itself with the component store', () => { diff --git a/scripts/sequence/components/Parallel.js b/scripts/sequence/components/Parallel.mjs similarity index 96% rename from scripts/sequence/components/Parallel.js rename to scripts/sequence/components/Parallel.mjs index 37b278d..225524f 100644 --- a/scripts/sequence/components/Parallel.js +++ b/scripts/sequence/components/Parallel.mjs @@ -1,8 +1,8 @@ import BaseComponent, { cleanRenderPreResult, register, -} from './BaseComponent.js'; -import {mergeSets} from '../../core/ArrayUtilities.js'; +} from './BaseComponent.mjs'; +import {mergeSets} from '../../core/ArrayUtilities.mjs'; function nullableMax(a = null, b = null) { if(a === null) { diff --git a/scripts/sequence/components/Parallel_spec.js b/scripts/sequence/components/Parallel_spec.mjs similarity index 69% rename from scripts/sequence/components/Parallel_spec.js rename to scripts/sequence/components/Parallel_spec.mjs index c0dbb7e..2e0ebd1 100644 --- a/scripts/sequence/components/Parallel_spec.js +++ b/scripts/sequence/components/Parallel_spec.mjs @@ -1,5 +1,5 @@ -import Parallel from './Parallel.js'; -import {getComponents} from './BaseComponent.js'; +import Parallel from './Parallel.mjs'; +import {getComponents} from './BaseComponent.mjs'; describe('Parallel', () => { it('registers itself with the component store', () => { diff --git a/scripts/sequence/themes/BaseTheme.js b/scripts/sequence/themes/BaseTheme.mjs similarity index 100% rename from scripts/sequence/themes/BaseTheme.js rename to scripts/sequence/themes/BaseTheme.mjs diff --git a/scripts/sequence/themes/Basic.js b/scripts/sequence/themes/Basic.mjs similarity index 99% rename from scripts/sequence/themes/Basic.js rename to scripts/sequence/themes/Basic.mjs index 254481c..955ff8e 100644 --- a/scripts/sequence/themes/Basic.js +++ b/scripts/sequence/themes/Basic.mjs @@ -1,6 +1,6 @@ /* eslint-disable sort-keys */ // Maybe later -import BaseTheme, {WavePattern} from './BaseTheme.js'; +import BaseTheme, {WavePattern} from './BaseTheme.mjs'; const FONT = 'sans-serif'; const LINE_HEIGHT = 1.3; diff --git a/scripts/sequence/themes/Basic_spec.js b/scripts/sequence/themes/Basic_spec.mjs similarity index 69% rename from scripts/sequence/themes/Basic_spec.js rename to scripts/sequence/themes/Basic_spec.mjs index 0aba8a2..863995b 100644 --- a/scripts/sequence/themes/Basic_spec.js +++ b/scripts/sequence/themes/Basic_spec.mjs @@ -1,6 +1,6 @@ -import {dom, textSizerFactory} from '../../stubs/TestDOM.js'; -import {Factory} from './Basic.js'; -import SVG from '../../svg/SVG.js'; +import {dom, textSizerFactory} from '../../../spec/stubs/TestDOM.mjs'; +import {Factory} from './Basic.mjs'; +import SVG from '../../svg/SVG.mjs'; describe('Basic Theme', () => { const svg = new SVG(dom, textSizerFactory); diff --git a/scripts/sequence/themes/Chunky.js b/scripts/sequence/themes/Chunky.mjs similarity index 99% rename from scripts/sequence/themes/Chunky.js rename to scripts/sequence/themes/Chunky.mjs index c8026a5..ca66ea1 100644 --- a/scripts/sequence/themes/Chunky.js +++ b/scripts/sequence/themes/Chunky.mjs @@ -1,6 +1,6 @@ /* eslint-disable sort-keys */ // Maybe later -import BaseTheme, {WavePattern} from './BaseTheme.js'; +import BaseTheme, {WavePattern} from './BaseTheme.mjs'; const FONT = 'sans-serif'; const LINE_HEIGHT = 1.3; diff --git a/scripts/sequence/themes/Chunky_spec.js b/scripts/sequence/themes/Chunky_spec.mjs similarity index 69% rename from scripts/sequence/themes/Chunky_spec.js rename to scripts/sequence/themes/Chunky_spec.mjs index 4a83bfd..84ac46f 100644 --- a/scripts/sequence/themes/Chunky_spec.js +++ b/scripts/sequence/themes/Chunky_spec.mjs @@ -1,6 +1,6 @@ -import {dom, textSizerFactory} from '../../stubs/TestDOM.js'; -import {Factory} from './Chunky.js'; -import SVG from '../../svg/SVG.js'; +import {dom, textSizerFactory} from '../../../spec/stubs/TestDOM.mjs'; +import {Factory} from './Chunky.mjs'; +import SVG from '../../svg/SVG.mjs'; describe('Chunky Theme', () => { const svg = new SVG(dom, textSizerFactory); diff --git a/scripts/sequence/themes/HandleeFontData.js b/scripts/sequence/themes/HandleeFontData.mjs similarity index 100% rename from scripts/sequence/themes/HandleeFontData.js rename to scripts/sequence/themes/HandleeFontData.mjs diff --git a/scripts/sequence/themes/Monospace.js b/scripts/sequence/themes/Monospace.mjs similarity index 99% rename from scripts/sequence/themes/Monospace.js rename to scripts/sequence/themes/Monospace.mjs index 139d21e..9402568 100644 --- a/scripts/sequence/themes/Monospace.js +++ b/scripts/sequence/themes/Monospace.mjs @@ -1,6 +1,6 @@ /* eslint-disable sort-keys */ // Maybe later -import BaseTheme, {WavePattern} from './BaseTheme.js'; +import BaseTheme, {WavePattern} from './BaseTheme.mjs'; const FONT = 'monospace'; const LINE_HEIGHT = 1.3; diff --git a/scripts/sequence/themes/Monospace_spec.js b/scripts/sequence/themes/Monospace_spec.mjs similarity index 69% rename from scripts/sequence/themes/Monospace_spec.js rename to scripts/sequence/themes/Monospace_spec.mjs index 45a6f42..c9decbe 100644 --- a/scripts/sequence/themes/Monospace_spec.js +++ b/scripts/sequence/themes/Monospace_spec.mjs @@ -1,6 +1,6 @@ -import {dom, textSizerFactory} from '../../stubs/TestDOM.js'; -import {Factory} from './Monospace.js'; -import SVG from '../../svg/SVG.js'; +import {dom, textSizerFactory} from '../../../spec/stubs/TestDOM.mjs'; +import {Factory} from './Monospace.mjs'; +import SVG from '../../svg/SVG.mjs'; describe('Monospace Theme', () => { const svg = new SVG(dom, textSizerFactory); diff --git a/scripts/sequence/themes/Sketch.js b/scripts/sequence/themes/Sketch.mjs similarity index 99% rename from scripts/sequence/themes/Sketch.js rename to scripts/sequence/themes/Sketch.mjs index c27f0c8..a9839a1 100644 --- a/scripts/sequence/themes/Sketch.js +++ b/scripts/sequence/themes/Sketch.mjs @@ -1,9 +1,9 @@ /* eslint-disable max-lines */ /* eslint-disable sort-keys */ // Maybe later -import BaseTheme from './BaseTheme.js'; -import Handlee from './HandleeFontData.js'; -import Random from '../../core/Random.js'; +import BaseTheme from './BaseTheme.mjs'; +import Handlee from './HandleeFontData.mjs'; +import Random from '../../core/Random.mjs'; const FONT = Handlee.name; const FONT_FAMILY = '\'' + FONT + '\',cursive'; diff --git a/scripts/sequence/themes/Sketch_spec.js b/scripts/sequence/themes/Sketch_spec.mjs similarity index 80% rename from scripts/sequence/themes/Sketch_spec.js rename to scripts/sequence/themes/Sketch_spec.mjs index f3e8f48..e0ed867 100644 --- a/scripts/sequence/themes/Sketch_spec.js +++ b/scripts/sequence/themes/Sketch_spec.mjs @@ -1,6 +1,6 @@ -import {dom, textSizerFactory} from '../../stubs/TestDOM.js'; -import {Factory} from './Sketch.js'; -import SVG from '../../svg/SVG.js'; +import {dom, textSizerFactory} from '../../../spec/stubs/TestDOM.mjs'; +import {Factory} from './Sketch.mjs'; +import SVG from '../../svg/SVG.mjs'; describe('Sketch Theme', () => { const svg = new SVG(dom, textSizerFactory); diff --git a/scripts/specs.js b/scripts/specs.js deleted file mode 100644 index 715fea1..0000000 --- a/scripts/specs.js +++ /dev/null @@ -1,37 +0,0 @@ -import './core/ArrayUtilities_spec.js'; -import './core/EventObject_spec.js'; -import './core/Random_spec.js'; -import './core/documents/VirtualDocument_spec.js'; -import './svg/SVG_spec.js'; -import './svg/SVGTextBlock_spec.js'; -import './svg/PatternedLine_spec.js'; -import './interface/Interface_spec.js'; -import './interface/ComponentsLibrary_spec.js'; -import './image/ImageRegion_spec.js'; -import './image/Blur_spec.js'; -import './image/Composition_spec.js'; -import './image/ImageSimilarity_spec.js'; -import './sequence/SequenceDiagram_spec.js'; -import './sequence/SequenceDiagram_visual_spec.js'; -import './sequence/Readme_spec.js'; -import './sequence/Tokeniser_spec.js'; -import './sequence/Parser_spec.js'; -import './sequence/MarkdownParser_spec.js'; -import './sequence/LabelPatternParser_spec.js'; -import './sequence/Generator_spec.js'; -import './sequence/Renderer_spec.js'; -import './sequence/CodeMirrorMode_spec.js'; -import './sequence/themes/Basic_spec.js'; -import './sequence/themes/Monospace_spec.js'; -import './sequence/themes/Chunky_spec.js'; -import './sequence/themes/Sketch_spec.js'; -import './sequence/components/AgentCap_spec.js'; -import './sequence/components/AgentHighlight_spec.js'; -import './sequence/components/Block_spec.js'; -import './sequence/components/Connect_spec.js'; -import './sequence/components/Divider_spec.js'; -import './sequence/components/Marker_spec.js'; -import './sequence/components/Note_spec.js'; -import './sequence/components/Parallel_spec.js'; - -jasmine.executeAllTests(); diff --git a/scripts/standalone.js b/scripts/standalone.mjs similarity index 84% rename from scripts/standalone.js rename to scripts/standalone.mjs index e0c5cc3..3af24dd 100644 --- a/scripts/standalone.js +++ b/scripts/standalone.mjs @@ -1,4 +1,4 @@ -import SequenceDiagram from './sequence/SequenceDiagram.js'; +import SequenceDiagram from './sequence/SequenceDiagram.mjs'; const def = window.define; if(def && def.amd) { diff --git a/scripts/svg/PatternedLine.js b/scripts/svg/PatternedLine.mjs similarity index 100% rename from scripts/svg/PatternedLine.js rename to scripts/svg/PatternedLine.mjs diff --git a/scripts/svg/PatternedLine_spec.js b/scripts/svg/PatternedLine_spec.mjs similarity index 98% rename from scripts/svg/PatternedLine_spec.js rename to scripts/svg/PatternedLine_spec.mjs index b616593..76190e6 100644 --- a/scripts/svg/PatternedLine_spec.js +++ b/scripts/svg/PatternedLine_spec.mjs @@ -1,4 +1,4 @@ -import PatternedLine from './PatternedLine.js'; +import PatternedLine from './PatternedLine.mjs'; describe('PatternedLine', () => { function simplify(path, dp) { diff --git a/scripts/svg/SVG.js b/scripts/svg/SVG.mjs similarity index 92% rename from scripts/svg/SVG.js rename to scripts/svg/SVG.mjs index 427e295..c9571ac 100644 --- a/scripts/svg/SVG.js +++ b/scripts/svg/SVG.mjs @@ -1,5 +1,5 @@ -import {SVGTextBlock, TextSizer} from './SVGTextBlock.js'; -import PatternedLine from './PatternedLine.js'; +import {SVGTextBlock, TextSizer} from './SVGTextBlock.mjs'; +import PatternedLine from './PatternedLine.mjs'; const NS = 'http://www.w3.org/2000/svg'; @@ -214,16 +214,16 @@ export default class SVG { return this.dom.el(tag, namespace); } - box(attrs, position) { - return this.el('rect').attrs(attrs).attrs(position); + box(attrs, {height, width, x, y}) { + return this.el('rect').attrs(attrs).attrs({height, width, x, y}); } boxFactory(attrs) { return this.box.bind(this, attrs); } - line(attrs, position) { - return this.el('line').attrs(attrs).attrs(position); + line(attrs, {x1, x2, y1, y2}) { + return this.el('line').attrs(attrs).attrs({x1, x2, y1, y2}); } lineFactory(attrs) { @@ -259,11 +259,11 @@ export default class SVG { return this.cross.bind(this, attrs); } - note(attrs, flickAttrs, position) { - const x0 = position.x; - const x1 = position.x + position.width; - const y0 = position.y; - const y1 = position.y + position.height; + note(attrs, flickAttrs, {height, width, x, y}) { + const x0 = x; + const x1 = x + width; + const y0 = y; + const y1 = y + height; const flick = 7; return this.el('g').add( @@ -290,13 +290,13 @@ export default class SVG { return this.note.bind(this, attrs, flickAttrs); } - formattedText(attrs = {}, formatted = [], position = {}) { + formattedText(attrs = {}, formatted = [], {x, y} = {}) { const container = this.el('g'); const txt = new SVGTextBlock(container, this, { attrs, formatted, - x: position.x, - y: position.y, + x, + y, }); return Object.assign(container, { set: (state) => txt.set(state), diff --git a/scripts/svg/SVGTextBlock.js b/scripts/svg/SVGTextBlock.mjs similarity index 96% rename from scripts/svg/SVGTextBlock.js rename to scripts/svg/SVGTextBlock.mjs index c5accbf..0e935bc 100644 --- a/scripts/svg/SVGTextBlock.js +++ b/scripts/svg/SVGTextBlock.mjs @@ -1,5 +1,4 @@ -// Thanks, https://stackoverflow.com/a/9851769/1180785 -const firefox = (typeof window.InstallTrigger !== 'undefined'); +import {firefox} from '../core/browser.mjs'; function merge(state, newState) { for(const k in state) { diff --git a/scripts/svg/SVGTextBlock_spec.js b/scripts/svg/SVGTextBlock_spec.mjs similarity index 95% rename from scripts/svg/SVGTextBlock_spec.js rename to scripts/svg/SVGTextBlock_spec.mjs index b1eb1ed..d3f8f68 100644 --- a/scripts/svg/SVGTextBlock_spec.js +++ b/scripts/svg/SVGTextBlock_spec.mjs @@ -1,6 +1,7 @@ -import {DOMWrapper, dom, textSizerFactory} from '../stubs/TestDOM.js'; -import {SVGTextBlock, TextSizer} from './SVGTextBlock.js'; -import SVG from './SVG.js'; +import {DOMWrapper, dom, textSizerFactory} from '../../spec/stubs/TestDOM.mjs'; +import {SVGTextBlock, TextSizer} from './SVGTextBlock.mjs'; +import SVG from './SVG.mjs'; +import {nodejs} from '../core/browser.mjs'; describe('SVGTextBlock', () => { const attrs = {'font-size': 10, 'line-height': 1.5}; @@ -128,6 +129,11 @@ describe('SVGTextBlock', () => { }); describe('TextSizer', () => { + if(nodejs) { + // TextSizer is for browsers only + return; + } + beforeEach(() => { svg = new SVG( new DOMWrapper(window.document), diff --git a/scripts/svg/SVG_spec.js b/scripts/svg/SVG_spec.mjs similarity index 97% rename from scripts/svg/SVG_spec.js rename to scripts/svg/SVG_spec.mjs index 593e05a..67e447a 100644 --- a/scripts/svg/SVG_spec.js +++ b/scripts/svg/SVG_spec.mjs @@ -1,5 +1,5 @@ -import {dom, textSizerFactory} from '../stubs/TestDOM.js'; -import SVG from './SVG.js'; +import {dom, textSizerFactory} from '../../spec/stubs/TestDOM.mjs'; +import SVG from './SVG.mjs'; describe('SVG', () => { const expectedNS = 'http://www.w3.org/2000/svg'; diff --git a/spec/helpers/toBeNear.mjs b/spec/helpers/toBeNear.mjs new file mode 100644 index 0000000..c4c851c --- /dev/null +++ b/spec/helpers/toBeNear.mjs @@ -0,0 +1,23 @@ +beforeAll(() => { + jasmine.addMatchers({ + toBeNear: () => ({ + compare: (actual, expected, range) => { + if( + typeof expected !== 'number' || + typeof range !== 'number' || + range < 0 + ) { + throw new Error( + 'Invalid toBeNear(' + expected + ',' + range + ')' + ); + } + if(typeof actual !== 'number') { + throw new Error('Expected a number, got ' + actual); + } + return { + pass: Math.abs(actual - expected) <= range, + }; + }, + }), + }); +}); diff --git a/scripts/sequence/test-images/AgentOptions.svg b/spec/images/AgentOptions.svg similarity index 100% rename from scripts/sequence/test-images/AgentOptions.svg rename to spec/images/AgentOptions.svg diff --git a/scripts/sequence/test-images/Asynchronous.svg b/spec/images/Asynchronous.svg similarity index 100% rename from scripts/sequence/test-images/Asynchronous.svg rename to spec/images/Asynchronous.svg diff --git a/scripts/sequence/test-images/Block.svg b/spec/images/Block.svg similarity index 100% rename from scripts/sequence/test-images/Block.svg rename to spec/images/Block.svg diff --git a/scripts/sequence/test-images/CollapsedBlocks.svg b/spec/images/CollapsedBlocks.svg similarity index 100% rename from scripts/sequence/test-images/CollapsedBlocks.svg rename to spec/images/CollapsedBlocks.svg diff --git a/scripts/sequence/test-images/Connect.svg b/spec/images/Connect.svg similarity index 100% rename from scripts/sequence/test-images/Connect.svg rename to spec/images/Connect.svg diff --git a/scripts/sequence/test-images/Divider.svg b/spec/images/Divider.svg similarity index 100% rename from scripts/sequence/test-images/Divider.svg rename to spec/images/Divider.svg diff --git a/scripts/sequence/test-images/DividerMasking.svg b/spec/images/DividerMasking.svg similarity index 100% rename from scripts/sequence/test-images/DividerMasking.svg rename to spec/images/DividerMasking.svg diff --git a/scripts/sequence/test-images/Markdown.svg b/spec/images/Markdown.svg similarity index 100% rename from scripts/sequence/test-images/Markdown.svg rename to spec/images/Markdown.svg diff --git a/scripts/sequence/test-images/Reference.svg b/spec/images/Reference.svg similarity index 100% rename from scripts/sequence/test-images/Reference.svg rename to spec/images/Reference.svg diff --git a/scripts/sequence/test-images/ReferenceLayering.svg b/spec/images/ReferenceLayering.svg similarity index 100% rename from scripts/sequence/test-images/ReferenceLayering.svg rename to spec/images/ReferenceLayering.svg diff --git a/scripts/sequence/test-images/SelfConnect.svg b/spec/images/SelfConnect.svg similarity index 100% rename from scripts/sequence/test-images/SelfConnect.svg rename to spec/images/SelfConnect.svg diff --git a/spec/images/Sketch.svg b/spec/images/Sketch.svg new file mode 100644 index 0000000..8fd474f --- /dev/null +++ b/spec/images/Sketch.svg @@ -0,0 +1,6 @@ +theme sketch + +A -> +B +B -> B: self +-B -x A +ABself \ No newline at end of file diff --git a/scripts/sequence/test-images/list.js b/spec/images/list.mjs similarity index 100% rename from scripts/sequence/test-images/list.js rename to spec/images/list.mjs diff --git a/scripts/stubs/TestDOM.js b/spec/stubs/TestDOM.mjs similarity index 80% rename from scripts/stubs/TestDOM.js rename to spec/stubs/TestDOM.mjs index d4f33c3..1c0fb12 100644 --- a/scripts/stubs/TestDOM.js +++ b/spec/stubs/TestDOM.mjs @@ -1,5 +1,8 @@ -import DOMWrapper from '../core/DOMWrapper.js'; -import VirtualDocument from '../core/documents/VirtualDocument.js'; +import { + Event, + VirtualDocument, +} from '../../scripts/core/documents/VirtualDocument.mjs'; +import DOMWrapper from '../../scripts/core/DOMWrapper.mjs'; export class UnitaryTextSizer { // Simplified text sizer, which assumes all characters render as @@ -37,6 +40,7 @@ export function textSizerFactory() { export const dom = new DOMWrapper(new VirtualDocument()); export { + Event, VirtualDocument, DOMWrapper, }; diff --git a/scripts/stubs/codemirror.js b/spec/stubs/codemirror.mjs similarity index 100% rename from scripts/stubs/codemirror.js rename to spec/stubs/codemirror.mjs diff --git a/scripts/stubs/require.js b/spec/stubs/require.mjs similarity index 72% rename from scripts/stubs/require.js rename to spec/stubs/require.mjs index 7b1747d..d3e52e6 100644 --- a/scripts/stubs/require.js +++ b/spec/stubs/require.mjs @@ -1,5 +1,5 @@ -import CodeMirror from './codemirror.js'; -import Split from './split.js'; +import CodeMirror from './codemirror.mjs'; +import Split from './split.mjs'; const stubLibs = new Map(); stubLibs.set('cm/lib/codemirror', CodeMirror); diff --git a/scripts/stubs/split.js b/spec/stubs/split.mjs similarity index 100% rename from scripts/stubs/split.js rename to spec/stubs/split.mjs diff --git a/spec/support/browser_specs.mjs b/spec/support/browser_specs.mjs new file mode 100644 index 0000000..6bc8356 --- /dev/null +++ b/spec/support/browser_specs.mjs @@ -0,0 +1,44 @@ +/* + * All spec files imported here will be tested in the browser + * when opening test.htm + */ + +import '../helpers/toBeNear.mjs'; + +import '../../scripts/core/ArrayUtilities_spec.mjs'; +import '../../scripts/core/EventObject_spec.mjs'; +import '../../scripts/core/Random_spec.mjs'; +import '../../scripts/core/documents/VirtualDocument_spec.mjs'; +import '../../scripts/svg/SVG_spec.mjs'; +import '../../scripts/svg/SVGTextBlock_spec.mjs'; +import '../../scripts/svg/PatternedLine_spec.mjs'; +import '../../scripts/image/ImageRegion_spec.mjs'; +import '../../scripts/image/Blur_spec.mjs'; +import '../../scripts/image/Composition_spec.mjs'; +import '../../scripts/image/ImageSimilarity_spec.mjs'; +import '../../scripts/sequence/SequenceDiagram_spec.mjs'; +import '../../scripts/sequence/Tokeniser_spec.mjs'; +import '../../scripts/sequence/Parser_spec.mjs'; +import '../../scripts/sequence/MarkdownParser_spec.mjs'; +import '../../scripts/sequence/LabelPatternParser_spec.mjs'; +import '../../scripts/sequence/Generator_spec.mjs'; +import '../../scripts/sequence/Renderer_spec.mjs'; +import '../../scripts/sequence/themes/Basic_spec.mjs'; +import '../../scripts/sequence/themes/Monospace_spec.mjs'; +import '../../scripts/sequence/themes/Chunky_spec.mjs'; +import '../../scripts/sequence/themes/Sketch_spec.mjs'; +import '../../scripts/sequence/components/AgentCap_spec.mjs'; +import '../../scripts/sequence/components/AgentHighlight_spec.mjs'; +import '../../scripts/sequence/components/Block_spec.mjs'; +import '../../scripts/sequence/components/Connect_spec.mjs'; +import '../../scripts/sequence/components/Divider_spec.mjs'; +import '../../scripts/sequence/components/Marker_spec.mjs'; +import '../../scripts/sequence/components/Note_spec.mjs'; +import '../../scripts/sequence/components/Parallel_spec.mjs'; + +import '../../web/interface/Interface_spec.mjs'; +import '../../web/interface/ComponentsLibrary_spec.mjs'; + +import '../../scripts/sequence/CodeMirrorMode_webspec.mjs'; +import '../../scripts/sequence/SequenceDiagram_visual_webspec.mjs'; +import '../../scripts/sequence/Readme_webspec.mjs'; diff --git a/spec/support/eslintignore b/spec/support/eslintignore new file mode 100644 index 0000000..bc6dbb4 --- /dev/null +++ b/spec/support/eslintignore @@ -0,0 +1,6 @@ +node_modules +lib +weblib +ephemeral + +*FontData.mjs diff --git a/.eslintrc.js b/spec/support/eslintrc.js similarity index 99% rename from .eslintrc.js rename to spec/support/eslintrc.js index 11beccb..36bcbfe 100644 --- a/.eslintrc.js +++ b/spec/support/eslintrc.js @@ -1,5 +1,3 @@ -'use strict'; - module.exports = { 'env': { 'browser': true, @@ -208,13 +206,13 @@ module.exports = { 'prefer-destructuring': [ 'error', { - 'VariableDeclarator': { - 'array': true, - 'object': true - }, 'AssignmentExpression': { 'array': false, - 'object': false + 'object': false, + }, + 'VariableDeclarator': { + 'array': true, + 'object': true, }, }, ], diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json new file mode 100644 index 0000000..1b54708 --- /dev/null +++ b/spec/support/jasmine.json @@ -0,0 +1,6 @@ +{ + "spec_dir": "ephemeral", + "spec_files": ["spec_bundle.js"], + "stopSpecOnExpectationFailure": false, + "random": true +} diff --git a/spec/support/rollup.config.js b/spec/support/rollup.config.js new file mode 100644 index 0000000..a46a903 --- /dev/null +++ b/spec/support/rollup.config.js @@ -0,0 +1,19 @@ +import multiEntry from 'rollup-plugin-multi-entry'; + +export default [ + { + input: [ + 'spec/helpers/**/*.js', + 'spec/helpers/**/*.mjs', + '**/*_spec.js', + '**/*_spec.mjs', + ], + output: { + file: 'ephemeral/spec_bundle.js', + format: 'iife', + name: 'Tests', + sourcemap: true, + }, + plugins: [multiEntry()], + }, +]; diff --git a/test.htm b/test.htm index 75681cf..d35fe28 100644 --- a/test.htm +++ b/test.htm @@ -47,10 +47,7 @@ crossorigin="anonymous" > - - - - + diff --git a/scripts/editor.js b/web/editor.mjs similarity index 83% rename from scripts/editor.js rename to web/editor.mjs index 05af256..80b3ff1 100644 --- a/scripts/editor.js +++ b/web/editor.mjs @@ -1,7 +1,7 @@ -import ComponentsLibrary from './interface/ComponentsLibrary.js'; -import Interface from './interface/Interface.js'; -import SequenceDiagram from './sequence/SequenceDiagram.js'; -import {require} from './requireCDN.js'; +import ComponentsLibrary from './interface/ComponentsLibrary.mjs'; +import Interface from './interface/Interface.mjs'; +import SequenceDiagram from '../scripts/sequence/SequenceDiagram.mjs'; +import {require} from './requireCDN.mjs'; const defaultCode = ( 'title Labyrinth\n' + diff --git a/scripts/interface/ComponentsLibrary.js b/web/interface/ComponentsLibrary.mjs similarity index 100% rename from scripts/interface/ComponentsLibrary.js rename to web/interface/ComponentsLibrary.mjs diff --git a/web/interface/ComponentsLibrary_spec.mjs b/web/interface/ComponentsLibrary_spec.mjs new file mode 100644 index 0000000..9ce3eb2 --- /dev/null +++ b/web/interface/ComponentsLibrary_spec.mjs @@ -0,0 +1,34 @@ +import {VirtualDocument, textSizerFactory} from '../../spec/stubs/TestDOM.mjs'; +import ComponentsLibrary from './ComponentsLibrary.mjs'; +import SequenceDiagram from '../../scripts/sequence/SequenceDiagram.mjs'; +import {nodejs} from '../../scripts/core/browser.mjs'; + +const themes = SequenceDiagram.getDefaultThemeNames().slice(1); + +const opts = nodejs ? { + document: new VirtualDocument(), + textSizerFactory, +} : {}; + +function checkSample(src) { + it('renders without error', () => { + expect(() => new SequenceDiagram(src, opts)).not.toThrow(); + }); + + themes.forEach((themeName) => { + it('renders without error in ' + themeName + ' theme', () => { + expect(() => new SequenceDiagram( + 'theme ' + themeName + '\n' + src, + opts + )).not.toThrow(); + }); + }); +} + +describe('Components Library', () => { + ComponentsLibrary.forEach(({title, code, preview}) => { + describe(title, () => { + checkSample(preview || code); + }); + }); +}); diff --git a/scripts/interface/Interface.js b/web/interface/Interface.mjs similarity index 92% rename from scripts/interface/Interface.js rename to web/interface/Interface.mjs index b11f415..bed4d8c 100644 --- a/scripts/interface/Interface.js +++ b/web/interface/Interface.mjs @@ -1,13 +1,11 @@ /* eslint-disable max-lines */ -import DOMWrapper from '../core/DOMWrapper.js'; +import DOMWrapper from '../../scripts/core/DOMWrapper.mjs'; const DELAY_AGENTCHANGE = 500; const DELAY_STAGECHANGE = 250; const PNG_RESOLUTION = 4; -const dom = new DOMWrapper(document); - function addNewline(value) { if(value.length > 0 && value.charAt(value.length - 1) !== '\n') { return value + '\n'; @@ -200,7 +198,7 @@ export default class Interface { } buildOptionsDownloads() { - this.downloadPNG = dom.el('a') + this.downloadPNG = this.dom.el('a') .text('Download PNG') .attrs({ 'download': 'SequenceDiagram.png', @@ -209,7 +207,7 @@ export default class Interface { .on(['focus', 'mouseover', 'mousedown'], this._downloadPNGFocus) .on('click', this._downloadPNGClick); - this.downloadSVG = dom.el('a') + this.downloadSVG = this.dom.el('a') .text('SVG') .attrs({ 'download': 'SequenceDiagram.svg', @@ -217,16 +215,16 @@ export default class Interface { }) .on('click', this._downloadSVGClick); - return dom.el('div').setClass('options downloads') + return this.dom.el('div').setClass('options downloads') .add(this.downloadPNG, this.downloadSVG); } buildLibrary(container) { const diagrams = this.library.map((lib) => { - const holdInner = dom.el('div') + const holdInner = this.dom.el('div') .attr('title', lib.title || lib.code); - const hold = dom.el('div') + const hold = this.dom.el('div') .setClass('library-item') .add(holdInner) .on('click', this.addCodeBlock.bind(this, lib.code)) @@ -253,36 +251,36 @@ export default class Interface { } buildViewPane() { - this.viewPaneInner = dom.el('div').setClass('pane-view-inner') + this.viewPaneInner = this.dom.el('div').setClass('pane-view-inner') .add(this.diagram.dom()); - this.errorMsg = dom.el('div').setClass('msg-error'); + this.errorMsg = this.dom.el('div').setClass('msg-error'); - return dom.el('div').setClass('pane-view') + return this.dom.el('div').setClass('pane-view') .add( - dom.el('div').setClass('pane-view-scroller') + this.dom.el('div').setClass('pane-view-scroller') .add(this.viewPaneInner), this.errorMsg ); } buildLeftPanes() { - const container = dom.el('div').setClass('pane-side'); + const container = this.dom.el('div').setClass('pane-side'); - this.code = dom.el('textarea') + this.code = this.dom.el('textarea') .setClass('editor-simple') .val(this.loadCode() || this.defaultCode) .on('input', () => this.update(false)); - const codePane = dom.el('div').setClass('pane-code') + const codePane = this.dom.el('div').setClass('pane-code') .add(this.code) .attach(container); if(this.library.length > 0) { - const libPane = dom.el('div').setClass('pane-library') - .add(dom.el('div').setClass('pane-library-scroller') + const libPane = this.dom.el('div').setClass('pane-library') + .add(this.dom.el('div').setClass('pane-library-scroller') .add(this.buildLibrary( - dom.el('div').setClass('pane-library-inner') + this.dom.el('div').setClass('pane-library-inner') ))) .attach(container); @@ -298,16 +296,17 @@ export default class Interface { } build(container) { + this.dom = new DOMWrapper(container.ownerDocument); const lPane = this.buildLeftPanes(); const viewPane = this.buildViewPane(); - this.container = dom.wrap(container) - .add(dom.el('div').setClass('pane-hold') + this.container = this.dom.wrap(container) + .add(this.dom.el('div').setClass('pane-hold') .add( lPane, viewPane, - dom.el('div').setClass('options links') - .add(this.links.map((link) => dom.el('a') + this.dom.el('div').setClass('options links') + .add(this.links.map((link) => this.dom.el('a') .attrs({'href': link.href, 'target': '_blank'}) .text(link.label))), this.buildOptionsDownloads() diff --git a/scripts/interface/Interface_spec.js b/web/interface/Interface_spec.mjs similarity index 70% rename from scripts/interface/Interface_spec.js rename to web/interface/Interface_spec.mjs index 85a8d49..fe1ee5f 100644 --- a/scripts/interface/Interface_spec.js +++ b/web/interface/Interface_spec.mjs @@ -1,16 +1,15 @@ -import Interface from './Interface.js'; -import stubRequire from '../stubs/require.js'; +import {Event, VirtualDocument} from '../../spec/stubs/TestDOM.mjs'; +import Interface from './Interface.mjs'; +import stubRequire from '../../spec/stubs/require.mjs'; describe('Interface', () => { - // Thanks, https://stackoverflow.com/a/23522755/1180785 - const safari = (/^((?!chrome|android).)*safari/i).test(navigator.userAgent); - const defaultCode = 'my default code'; let sequenceDiagram = null; let container = null; let ui = null; beforeEach(() => { + const dom = new VirtualDocument(); sequenceDiagram = jasmine.createSpyObj('sequenceDiagram', [ 'dom', 'render', @@ -29,11 +28,8 @@ describe('Interface', () => { }); sequenceDiagram.on.and.returnValue(sequenceDiagram); sequenceDiagram.getSize.and.returnValue({height: 20, width: 10}); - sequenceDiagram.dom.and.returnValue(document.createElement('svg')); - container = jasmine.createSpyObj('container', [ - 'insertBefore', - 'addEventListener', - ]); + sequenceDiagram.dom.and.returnValue(dom.createElement('svg')); + container = dom.createElement('div'); ui = new Interface({ defaultCode, @@ -46,10 +42,7 @@ describe('Interface', () => { it('adds elements to the given container', () => { ui.build(container); - expect(container.insertBefore).toHaveBeenCalledWith( - jasmine.anything(), - null - ); + expect(container.childNodes.length).toBeGreaterThan(0); }); it('creates a code mirror instance with the given code', (done) => { @@ -76,13 +69,6 @@ describe('Interface', () => { expect(el.getAttribute('href')).toEqual('#'); - if(safari) { - /* - * Safari actually starts a download if we do this, which - * doesn't seem to fit its usual security vibe - */ - return; - } el.dispatchEvent(new Event('click')); expect(el.getAttribute('href')).toEqual('mySVGURL'); diff --git a/scripts/readmeImages.js b/web/readmeImages.mjs similarity index 96% rename from scripts/readmeImages.js rename to web/readmeImages.mjs index d87206a..32be5ee 100644 --- a/scripts/readmeImages.js +++ b/web/readmeImages.mjs @@ -1,4 +1,4 @@ -import SequenceDiagram from './sequence/SequenceDiagram.js'; +import SequenceDiagram from '../scripts/sequence/SequenceDiagram.mjs'; function makeText(text = '') { return document.createTextNode(text); diff --git a/scripts/requireCDN.js b/web/requireCDN.mjs similarity index 88% rename from scripts/requireCDN.js rename to web/requireCDN.mjs index 844871e..c8c6591 100644 --- a/scripts/requireCDN.js +++ b/web/requireCDN.mjs @@ -10,7 +10,9 @@ for(let i = 0; i < metaTags.length; ++ i) { if(name && name.startsWith('cdn-')) { const module = name.substr('cdn-'.length); let src = metaTag.getAttribute('content'); - if(src.endsWith('.js')) { + if(src.endsWith('.mjs')) { + src = src.substr(0, src.length - '.mjs'.length); + } else if(src.endsWith('.js')) { src = src.substr(0, src.length - '.js'.length); } paths[module] = src; diff --git a/rollup.config.js b/web/rollup.config.js similarity index 50% rename from rollup.config.js rename to web/rollup.config.js index be55d41..64abbb9 100644 --- a/rollup.config.js +++ b/web/rollup.config.js @@ -2,28 +2,20 @@ import hypothetical from 'rollup-plugin-hypothetical'; export default [ { - input: 'scripts/standalone.js', + input: 'web/editor.mjs', output: { + file: 'weblib/editor.js', format: 'iife', - file: 'lib/sequence-diagram.js', - name: 'SequenceDiagram', + name: 'SequenceDiagramEditor', }, - }, - { - input: 'scripts/editor.js', plugins: [ hypothetical({ allowFallthrough: true, files: { - './scripts/sequence/SequenceDiagram.js': - 'export default window.SequenceDiagram;' + './scripts/sequence/SequenceDiagram.mjs': + 'export default window.SequenceDiagram;', }, }), ], - output: { - format: 'iife', - file: 'weblib/editor.js', - name: 'SequenceDiagramEditor', - }, }, -] +]; diff --git a/weblib/editor.js b/weblib/editor.js index 87033f6..4a453b1 100644 --- a/weblib/editor.js +++ b/weblib/editor.js @@ -510,8 +510,6 @@ const DELAY_STAGECHANGE = 250; const PNG_RESOLUTION = 4; - const dom = new DOMWrapper(document); - function addNewline(value) { if(value.length > 0 && value.charAt(value.length - 1) !== '\n') { return value + '\n'; @@ -704,7 +702,7 @@ } buildOptionsDownloads() { - this.downloadPNG = dom.el('a') + this.downloadPNG = this.dom.el('a') .text('Download PNG') .attrs({ 'download': 'SequenceDiagram.png', @@ -713,7 +711,7 @@ .on(['focus', 'mouseover', 'mousedown'], this._downloadPNGFocus) .on('click', this._downloadPNGClick); - this.downloadSVG = dom.el('a') + this.downloadSVG = this.dom.el('a') .text('SVG') .attrs({ 'download': 'SequenceDiagram.svg', @@ -721,16 +719,16 @@ }) .on('click', this._downloadSVGClick); - return dom.el('div').setClass('options downloads') + return this.dom.el('div').setClass('options downloads') .add(this.downloadPNG, this.downloadSVG); } buildLibrary(container) { const diagrams = this.library.map((lib) => { - const holdInner = dom.el('div') + const holdInner = this.dom.el('div') .attr('title', lib.title || lib.code); - const hold = dom.el('div') + const hold = this.dom.el('div') .setClass('library-item') .add(holdInner) .on('click', this.addCodeBlock.bind(this, lib.code)) @@ -757,36 +755,36 @@ } buildViewPane() { - this.viewPaneInner = dom.el('div').setClass('pane-view-inner') + this.viewPaneInner = this.dom.el('div').setClass('pane-view-inner') .add(this.diagram.dom()); - this.errorMsg = dom.el('div').setClass('msg-error'); + this.errorMsg = this.dom.el('div').setClass('msg-error'); - return dom.el('div').setClass('pane-view') + return this.dom.el('div').setClass('pane-view') .add( - dom.el('div').setClass('pane-view-scroller') + this.dom.el('div').setClass('pane-view-scroller') .add(this.viewPaneInner), this.errorMsg ); } buildLeftPanes() { - const container = dom.el('div').setClass('pane-side'); + const container = this.dom.el('div').setClass('pane-side'); - this.code = dom.el('textarea') + this.code = this.dom.el('textarea') .setClass('editor-simple') .val(this.loadCode() || this.defaultCode) .on('input', () => this.update(false)); - const codePane = dom.el('div').setClass('pane-code') + const codePane = this.dom.el('div').setClass('pane-code') .add(this.code) .attach(container); if(this.library.length > 0) { - const libPane = dom.el('div').setClass('pane-library') - .add(dom.el('div').setClass('pane-library-scroller') + const libPane = this.dom.el('div').setClass('pane-library') + .add(this.dom.el('div').setClass('pane-library-scroller') .add(this.buildLibrary( - dom.el('div').setClass('pane-library-inner') + this.dom.el('div').setClass('pane-library-inner') ))) .attach(container); @@ -802,16 +800,17 @@ } build(container) { + this.dom = new DOMWrapper(container.ownerDocument); const lPane = this.buildLeftPanes(); const viewPane = this.buildViewPane(); - this.container = dom.wrap(container) - .add(dom.el('div').setClass('pane-hold') + this.container = this.dom.wrap(container) + .add(this.dom.el('div').setClass('pane-hold') .add( lPane, viewPane, - dom.el('div').setClass('options links') - .add(this.links.map((link) => dom.el('a') + this.dom.el('div').setClass('options links') + .add(this.links.map((link) => this.dom.el('a') .attrs({'href': link.href, 'target': '_blank'}) .text(link.label))), this.buildOptionsDownloads() @@ -1134,7 +1133,9 @@ if(name && name.startsWith('cdn-')) { const module = name.substr('cdn-'.length); let src = metaTag.getAttribute('content'); - if(src.endsWith('.js')) { + if(src.endsWith('.mjs')) { + src = src.substr(0, src.length - '.mjs'.length); + } else if(src.endsWith('.js')) { src = src.substr(0, src.length - '.js'.length); } paths[module] = src; diff --git a/weblib/editor.min.js b/weblib/editor.min.js index 151e6a9..d09cec8 100644 --- a/weblib/editor.min.js +++ b/weblib/editor.min.js @@ -1 +1 @@ -!function(){"use strict";function e(e){return null===e?null:e.element?e.element:e}function t(e){return e.length>0&&"\n"!==e.charAt(e.length-1)?e+"\n":e}function n(e,t){let n=0,i=0;for(;;){const s=e.indexOf("\n",n)+1;if(t{const i=t[0].parentNode,s=i.addEventListener,r=i.removeEventListener;i.addEventListener=((e,t)=>{"mousemove"===e||"touchmove"===e?window.addEventListener(e,t,{passive:!0}):s.call(i,e,t)}),i.removeEventListener=((e,t)=>{"mousemove"===e||"touchmove"===e?window.removeEventListener(e,t):r.call(i,e,t)});let o=null;const a=Object.assign({cursor:"vertical"===n.direction?"row-resize":"col-resize",direction:"vertical",gutterSize:0,onDragEnd:()=>{document.body.style.cursor=o,o=null},onDragStart:()=>{o=document.body.style.cursor,document.body.style.cursor=a.cursor}},n);return new e(t,a)})}var s=[{code:"{Agent1} -> {Agent2}: {Message}",title:"Simple arrow (synchronous)"},{code:"{Agent1} --\x3e {Agent2}: {Message}",title:"Arrow with dotted line (response)"},{code:"{Agent1} ->> {Agent2}: {Message}",title:"Open arrow (asynchronous)"},{code:"{Agent1} -x {Agent2}: {Message}",title:"Lost message"},{code:"{Agent1} ~> {Agent2}: {Message}",title:"Wavy line"},{code:"{Agent1} -> {Agent1}: {Message}",title:"Self-connection"},{code:"{Agent1} -> ...{id}\n...{id} -> {Agent2}: {Message}",preview:"begin A, B\nA -> ...x\n...x -> B: Message",title:"Asynchronous message"},{code:"* -> {Agent1}: {Message}",title:"Found message"},{code:"{Agent1} -> +{Agent2}: {Request}\n{Agent1} <-- -{Agent2}: {Response}",title:"Request/response pair"},{code:"{Agent1} -> *{Agent2}: {Request}\n{Agent1} <-- !{Agent2}: {Response}",title:"Inline agent creation / destruction"},{code:"{Agent1} -> {Agent2}: {Request}\n{Agent1} <-- {Agent2}: {Response}\nend {Agent2}",preview:"begin A\n::\nA -> B: Request\nA <-- B: Response\nend B",title:"Agent creation / destruction"},{code:'autolabel "[]