Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
92513c2
install dotenv
lee-treehouse Dec 29, 2025
e927013
run on configurable port
lee-treehouse Dec 29, 2025
d7c42eb
test: can we use baseurl with cypress instead of specifying each time?
lee-treehouse Dec 29, 2025
bcf3d49
reference base url in matchers
lee-treehouse Dec 29, 2025
ae2d899
expose port config to cypress
lee-treehouse Dec 29, 2025
0b434c2
remove baseUrl duplication
lee-treehouse Dec 30, 2025
bdca4cf
add port to docker compose and docker file
lee-treehouse Dec 30, 2025
4c1065a
rename $PORT to $MERMAID_PORT so can introduce docs port
lee-treehouse Dec 30, 2025
38e8d99
use dotenv-cli to get port variable from .env and pass into cypress h…
lee-treehouse Dec 30, 2025
a5fe0ea
load env when calling e2e commands and use custom port in vite
lee-treehouse Dec 30, 2025
c2bec02
return default port to 9000 to keep existing behaviour
lee-treehouse Dec 31, 2025
00b1eee
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 31, 2025
a292399
this makes docker work, but is it desirable?
lee-treehouse Jan 17, 2026
7cc17b5
Merge branch 'update-node-minor-version' of github.com:lee-treehouse/…
lee-treehouse Jan 17, 2026
cc18679
Merge branch 'chore/7282_allow-for-port-to-be-overriden-in-single-pla…
lee-treehouse Jan 17, 2026
7fc5609
demonstrate docker port change works successfully
lee-treehouse Jan 17, 2026
b42f0f9
add some explanatory documentation
lee-treehouse Jan 17, 2026
d247f6d
Revert "this makes docker work, but is it desirable?"
lee-treehouse Jan 17, 2026
6bd30f6
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 17, 2026
ae448c4
Merge pull request #7283 from lee-treehouse/chore/7282_allow-for-port…
ashishjain0512 Jan 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MERMAID_PORT=9000
6 changes: 4 additions & 2 deletions .esbuild/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import express from 'express';
import { packageOptions } from '../.build/common.js';
import { generateLangium } from '../.build/generateLangium.js';
import { defaultOptions, getBuildConfig } from './util.js';
import 'dotenv/config';

const configs = Object.values(packageOptions).map(({ packageName }) =>
getBuildConfig({
Expand Down Expand Up @@ -88,6 +89,7 @@ async function createServer() {
await generateLangium();
handleFileChange();
const app = express();
const port = process.env.MERMAID_PORT ?? 9000;
chokidar
.watch('**/src/**/*.{js,ts,langium,yaml,json}', {
ignoreInitial: true,
Expand All @@ -114,8 +116,8 @@ async function createServer() {
app.use(express.static('demos'));
app.use(express.static('cypress/platform'));

app.listen(9000, () => {
console.log(`Listening on http://localhost:9000`);
app.listen(port, () => {
console.log(`Listening on http://localhost:${port}`);
});
}

Expand Down
5 changes: 3 additions & 2 deletions .vite/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { packageOptions } from '../.build/common.js';

async function createServer() {
const app = express();
const port = process.env.MERMAID_PORT ?? 9000;

// Create Vite server in middleware mode
const vite = await createViteServer({
Expand All @@ -22,9 +23,9 @@ async function createServer() {
app.use(express.static('demos'));
app.use(express.static('cypress/platform'));

app.listen(9000, () => {
app.listen(port, () => {
// eslint-disable-next-line no-console
console.log(`Listening on http://localhost:9000`);
console.log(`Listening on http://localhost:${port}`);
});
}

Expand Down
2 changes: 2 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import coverage from '@cypress/code-coverage/task.js';
import { defineConfig } from 'cypress';
import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin.js';
import cypressSplit from 'cypress-split';
import 'dotenv/config';

export default eyesPlugin(
defineConfig({
projectId: 'n2sma2',
viewportWidth: 1440,
viewportHeight: 1024,
e2e: {
baseUrl: `http://localhost:${process.env.MERMAID_PORT ?? 9000}`,
specPattern: 'cypress/integration/**/*.{js,ts}',
setupNodeEvents(on, config) {
coverage(on, config);
Expand Down
4 changes: 2 additions & 2 deletions cypress/helpers/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export const mermaidUrl = (
mermaid: options,
};
const objStr: string = JSON.stringify(codeObject);
let url = `http://localhost:9000/e2e.html?graph=${utf8ToB64(objStr)}`;
let url = `/e2e.html?graph=${utf8ToB64(objStr)}`;
if (api && typeof graphStr === 'string') {
url = `http://localhost:9000/xss.html?graph=${graphStr}`;
url = `/xss.html?graph=${graphStr}`;
}

if (options.listUrl) {
Expand Down
6 changes: 3 additions & 3 deletions cypress/integration/other/configuration.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe('Configuration', () => {
});
});
it('should not taint the initial configuration when using multiple directives', () => {
const url = 'http://localhost:9000/regression/issue-1874.html';
const url = '/regression/issue-1874.html';
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
verifyScreenshot(
Expand All @@ -134,7 +134,7 @@ describe('Configuration', () => {
});

it('should not render error diagram if suppressErrorRendering is set', () => {
const url = 'http://localhost:9000/suppressError.html?suppressErrorRendering=true';
const url = '/suppressError.html?suppressErrorRendering=true';
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
cy.get('#test')
Expand All @@ -151,7 +151,7 @@ describe('Configuration', () => {
});

it('should render error diagram if suppressErrorRendering is not set', () => {
const url = 'http://localhost:9000/suppressError.html';
const url = '/suppressError.html';
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
cy.get('#test')
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/other/external-diagrams.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { urlSnapshotTest } from '../../helpers/util.ts';
describe('mermaid', () => {
describe('registerDiagram', () => {
it('should work on @mermaid-js/mermaid-example-diagram', () => {
const url = 'http://localhost:9000/external-diagrams-example-diagram.html';
const url = '/external-diagrams-example-diagram.html';
urlSnapshotTest(url, {}, false, false);
});
});
Expand Down
6 changes: 3 additions & 3 deletions cypress/integration/other/ghsa.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.t

describe('CSS injections', () => {
it('should not allow CSS injections outside of the diagram', () => {
urlSnapshotTest('http://localhost:9000/ghsa1.html', {
urlSnapshotTest('/ghsa1.html', {
logLevel: 1,
flowchart: { htmlLabels: false },
});
});
it('should not allow adding styletags affecting the page', () => {
urlSnapshotTest('http://localhost:9000/ghsa3.html', {
urlSnapshotTest('/ghsa3.html', {
logLevel: 1,
flowchart: { htmlLabels: false },
});
});
it('should not allow manipulating styletags using arrowheads', () => {
openURLAndVerifyRendering('http://localhost:9000/xss23-css.html', {
openURLAndVerifyRendering('/xss23-css.html', {
logLevel: 1,
arrowMarkerAbsolute: false,
flowchart: { htmlLabels: true },
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/other/iife.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
describe('IIFE', () => {
beforeEach(() => {
cy.visit('http://localhost:9000/iife.html');
cy.visit('/iife.html');
});

it('should render when using mermaid.min.js', () => {
Expand Down
30 changes: 16 additions & 14 deletions cypress/integration/other/interaction.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
describe('Interaction', () => {
const baseUrl = Cypress.config('baseUrl');

describe('Security level loose', () => {
beforeEach(() => {
cy.visit('http://localhost:9000/click_security_loose.html');
cy.visit('/click_security_loose.html');
});

it('Graph: should handle a click on a node with a bound function', () => {
Expand All @@ -23,14 +25,14 @@ describe('Interaction', () => {
// When there is a URL, `cy.contains()` selects the `a` tag instead of the `span` tag. The .node is a child of `a`, so we have to use `find()` instead of `parent`.
cy.contains('URLTest1').find('.node').click();
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
expect(href).to.eq(`${baseUrl}/empty.html`);
});
});

it('Graph: should handle a click on a node with a bound url where the node starts with a number', () => {
cy.contains('2URL').find('.node').click();
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
expect(href).to.eq(`${baseUrl}/empty.html`);
});
});

Expand All @@ -47,28 +49,28 @@ describe('Interaction', () => {
it('Flowchart-v2: should handle a click on a node with a bound url', () => {
cy.contains('URLTest2').find('.node').click();
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
expect(href).to.eq(`${baseUrl}/empty.html`);
});
});

it('Flowchart-v2: should handle a click on a node with a bound url where the node starts with a number', () => {
cy.contains('20URL').find('.node').click();
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
expect(href).to.eq(`${baseUrl}/empty.html`);
});
});

it('should handle a click on a task with a bound URL clicking on the rect', () => {
cy.get('rect#cl1').click({ force: true });
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
expect(href).to.eq(`${baseUrl}/empty.html`);
});
});

it('should handle a click on a task with a bound URL clicking on the text', () => {
cy.get('text#cl1-text').click({ force: true });
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
expect(href).to.eq(`${baseUrl}/empty.html`);
});
});

Expand All @@ -95,7 +97,7 @@ describe('Interaction', () => {

describe('Interaction - security level tight', () => {
beforeEach(() => {
cy.visit('http://localhost:9000/click_security_strict.html');
cy.visit('/click_security_strict.html');
});
it('should handle a click on a node without a bound function', () => {
cy.contains('Function1').parents('.node').click();
Expand All @@ -110,28 +112,28 @@ describe('Interaction', () => {
it('should handle a click on a node with a bound url', () => {
cy.contains('URL1').find('.node').click();
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
expect(href).to.eq(`${baseUrl}/empty.html`);
});
});

it('should handle a click on a node with a bound url where the node starts with a number', () => {
cy.contains('2URL').find('.node').click();
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
expect(href).to.eq(`${baseUrl}/empty.html`);
});
});

it('should handle a click on a task with a bound URL clicking on the rect', () => {
cy.get('rect#cl1').click({ force: true });
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
expect(href).to.eq(`${baseUrl}/empty.html`);
});
});

it('should handle a click on a task with a bound URL clicking on the text', () => {
cy.get('text#cl1-text').click({ force: true });
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
expect(href).to.eq(`${baseUrl}/empty.html`);
});
});

Expand All @@ -148,7 +150,7 @@ describe('Interaction', () => {

describe('Interaction - security level other, misspelling', () => {
beforeEach(() => {
cy.visit('http://localhost:9000/click_security_other.html');
cy.visit('/click_security_other.html');
});

it('should handle a click on a node with a bound function', () => {
Expand All @@ -164,7 +166,7 @@ describe('Interaction', () => {
it('should handle a click on a node with a bound url', () => {
cy.contains('URL1').find('.node').click();
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
expect(href).to.eq(`${baseUrl}/empty.html`);
});
});

Expand Down
4 changes: 2 additions & 2 deletions cypress/integration/other/rerender.spec.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
describe('Rerendering', () => {
it('should be able to render after an error has occurred', () => {
const url = 'http://localhost:9000/render-after-error.html';
const url = '/render-after-error.html';
cy.visit(url);
cy.get('#graphDiv').should('exist');
});

it('should be able to render and rerender a graph via API', () => {
const url = 'http://localhost:9000/rerender.html';
const url = '/rerender.html';
cy.visit(url);
cy.get('#graph [id^=flowchart-A]').should('have.text', 'XMas');

Expand Down
32 changes: 16 additions & 16 deletions cypress/integration/other/xss.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,89 +55,89 @@ describe('XSS', () => {
});

it('should not allow changing the __proto__ attribute using config', () => {
cy.visit('http://localhost:9000/xss2.html');
cy.visit('/xss2.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should not allow manipulating htmlLabels into a false positive', () => {
cy.visit('http://localhost:9000/xss4.html');
cy.visit('/xss4.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should not allow manipulating antiscript to run javascript', () => {
cy.visit('http://localhost:9000/xss5.html');
cy.visit('/xss5.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should not allow manipulating antiscript to run javascript using onerror', () => {
cy.visit('http://localhost:9000/xss6.html');
cy.visit('/xss6.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should not allow manipulating antiscript to run javascript using onerror in state diagrams with dagre wrapper', () => {
cy.visit('http://localhost:9000/xss8.html');
cy.visit('/xss8.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should not allow manipulating antiscript to run javascript using onerror in state diagrams with dagre d3', () => {
cy.on('uncaught:exception', (_err, _runnable) => {
return false; // continue rendering even if there if mermaid throws an error
});
cy.visit('http://localhost:9000/xss9.html');
cy.visit('/xss9.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should not allow manipulating antiscript to run javascript using onerror in state diagrams with dagre d3', () => {
cy.visit('http://localhost:9000/xss10.html');
cy.visit('/xss10.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should not allow manipulating antiscript to run javascript using onerror in state diagrams with dagre d3', () => {
cy.visit('http://localhost:9000/xss11.html');
cy.visit('/xss11.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should not allow manipulating antiscript to run javascript using onerror in state diagrams with dagre d3', () => {
cy.visit('http://localhost:9000/xss12.html');
cy.visit('/xss12.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should not allow manipulating antiscript to run javascript using onerror in state diagrams with dagre d3', () => {
cy.visit('http://localhost:9000/xss13.html');
cy.visit('/xss13.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should not allow manipulating antiscript to run javascript iframes in class diagrams', () => {
cy.visit('http://localhost:9000/xss14.html');
cy.visit('/xss14.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should sanitize cardinalities properly in class diagrams', () => {
cy.visit('http://localhost:9000/xss18.html');
cy.visit('/xss18.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should sanitize colons properly', () => {
cy.visit('http://localhost:9000/xss20.html');
cy.visit('/xss20.html');
cy.wait(1000);
cy.get('a').click('');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should sanitize colons properly', () => {
cy.visit('http://localhost:9000/xss21.html');
cy.visit('/xss21.html');
cy.wait(1000);
cy.get('a').click('');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should sanitize backticks in class names properly', () => {
cy.visit('http://localhost:9000/xss24.html');
cy.visit('/xss24.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should sanitize backticks block diagram labels properly', () => {
cy.visit('http://localhost:9000/xss25.html');
cy.visit('/xss25.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/rendering/architecture.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,6 @@ describe.skip('architecture diagram', () => {
// Skipped as the layout is not deterministic, and causes issues in E2E tests.
describe.skip('architecture - external', () => {
it('should allow adding external icons', () => {
urlSnapshotTest('http://localhost:9000/architecture-external.html');
urlSnapshotTest('/architecture-external.html');
});
});
Loading
Loading