mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 09:19:34 +05:30
fix(flowchart): replace window.prompt/confirm with inline modal in standalone window; v4.9.9
Electron renderer contexts return undefined from window.prompt/window.confirm, so shape change, edge kind, edge label, and reset confirmation did nothing. Bundle now ships promptInline/confirmInline (custom DOM overlay modals) and uses them in onEdgeClick / onShapeMenu / reset handler. Exposed as window.FlowchartModals for jsdom tests. Amit Haridas
This commit is contained in:
@@ -257,4 +257,4 @@ Amit Haridas (amit.wh@gmail.com)
|
|||||||
|
|
||||||
## Version
|
## Version
|
||||||
|
|
||||||
v4.9.8
|
v4.9.9
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# PanConverter - Updates & Changelog
|
# PanConverter - Updates & Changelog
|
||||||
|
|
||||||
|
## Version 4.9.9 (2026-09-15)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
- **Standalone Flowchart Generator window: replaced broken `window.prompt` / `window.confirm` with an inline DOM-modal dialog.** Electron renderer contexts (the BrowserWindow hosting the standalone window) return `undefined` when `window.prompt(...)` or `window.confirm(...)` is called — meaning every shape menu, edge-kind change, edge-label edit, and reset confirmation silently did nothing. The bundle now ships two helpers (`promptInline`, `confirmInline`) that build a small overlay with a styled title, message, OK / Cancel buttons, and Enter / Escape / backdrop-click handling. The four call sites (`onEdgeClick` for kind + label, `onShapeMenu`, and the Reset click handler) are now `async` and await the helpers.
|
||||||
|
- New `window.FlowchartModals = { promptInline, confirmInline }` export on the bundle so jsdom tests can drive the modals directly without rebuilding the IIFE.
|
||||||
|
- 6 new tests in `tests/flowchart-controller.test.js` — OK / Cancel / Escape resolution paths for `promptInline`, OK / Cancel for `confirmInline`, and the `danger` flag renders a red "Delete" primary button.
|
||||||
|
|
||||||
## Version 4.9.8 (2026-09-15)
|
## Version 4.9.8 (2026-09-15)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "markdown-converter",
|
"name": "markdown-converter",
|
||||||
"version": "4.9.8",
|
"version": "4.9.9",
|
||||||
"description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting",
|
"description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* v4.9.8 — Bundled single-file Flowchart Generator loader.
|
* v4.9.9 — Bundled single-file Flowchart Generator loader.
|
||||||
*
|
*
|
||||||
* Inlines the four pure modules (flowchart-shapes / flowchart-mermaid /
|
* Inlines the four pure modules (flowchart-shapes / flowchart-mermaid /
|
||||||
* flowchart-store / flowchart-canvas) plus the renderer controller
|
* flowchart-store / flowchart-canvas) plus the renderer controller
|
||||||
@@ -19,6 +19,11 @@
|
|||||||
* brute-force bundle everything into one self-contained file. No
|
* brute-force bundle everything into one self-contained file. No
|
||||||
* cross-file script ordering, no UMD wrapper, no `require()`. The
|
* cross-file script ordering, no UMD wrapper, no `require()`. The
|
||||||
* standalone window now has exactly one script dependency.
|
* standalone window now has exactly one script dependency.
|
||||||
|
* v4.9.9 — Electron renderer contexts disable `window.prompt` and
|
||||||
|
* `window.confirm`, so shape change / edge kind / edge label /
|
||||||
|
* reset confirmation did nothing. Replaced with `promptInline`
|
||||||
|
* and `confirmInline` (custom DOM-overlay modals). Exposed as
|
||||||
|
* `window.FlowchartModals` for jsdom tests.
|
||||||
*
|
*
|
||||||
* The legacy individual files under src/flowchart/* and
|
* The legacy individual files under src/flowchart/* and
|
||||||
* src/renderer/flowchart-controller.js are kept untouched — the
|
* src/renderer/flowchart-controller.js are kept untouched — the
|
||||||
@@ -814,6 +819,218 @@
|
|||||||
}, PERSIST_DEBOUNCE_MS);
|
}, PERSIST_DEBOUNCE_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== Inline modal dialog (replacement for window.prompt/confirm) ==========
|
||||||
|
// v4.9.9 — window.prompt and window.confirm are disabled in Electron
|
||||||
|
// renderer contexts (the BrowserWindow of a BrowserView/WebContentsView
|
||||||
|
// returns undefined when called). Build minimal modal interactions on top
|
||||||
|
// of plain DOM nodes. Resolves with the entered string (or null on
|
||||||
|
// cancel/Esc/backdrop-click) for promptInline, and with a boolean for
|
||||||
|
// confirmInline.
|
||||||
|
function promptInline({ title, message, defaultValue = '', kind = 'text' }) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
Object.assign(overlay.style, {
|
||||||
|
position: 'fixed',
|
||||||
|
inset: '0',
|
||||||
|
background: 'rgba(0,0,0,0.45)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: 99999,
|
||||||
|
});
|
||||||
|
const box = document.createElement('div');
|
||||||
|
Object.assign(box.style, {
|
||||||
|
background: '#ffffff',
|
||||||
|
color: '#1f2328',
|
||||||
|
border: '1px solid #d0d7de',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '20px 24px',
|
||||||
|
minWidth: '320px',
|
||||||
|
maxWidth: '480px',
|
||||||
|
boxShadow: '0 12px 32px rgba(0,0,0,0.25)',
|
||||||
|
fontFamily: 'system-ui, sans-serif',
|
||||||
|
});
|
||||||
|
if (title) {
|
||||||
|
const h = document.createElement('div');
|
||||||
|
h.textContent = title;
|
||||||
|
Object.assign(h.style, { fontSize: '14px', fontWeight: '600', marginBottom: '12px' });
|
||||||
|
box.appendChild(h);
|
||||||
|
}
|
||||||
|
if (message) {
|
||||||
|
const m = document.createElement('div');
|
||||||
|
m.textContent = message;
|
||||||
|
Object.assign(m.style, {
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#57606a',
|
||||||
|
marginBottom: '12px',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
});
|
||||||
|
box.appendChild(m);
|
||||||
|
}
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = kind === 'number' ? 'number' : 'text';
|
||||||
|
input.value = defaultValue;
|
||||||
|
Object.assign(input.style, {
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px 10px',
|
||||||
|
fontSize: '13px',
|
||||||
|
border: '1px solid #d0d7de',
|
||||||
|
borderRadius: '4px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
});
|
||||||
|
box.appendChild(input);
|
||||||
|
|
||||||
|
const buttons = document.createElement('div');
|
||||||
|
Object.assign(buttons.style, {
|
||||||
|
marginTop: '14px',
|
||||||
|
display: 'flex',
|
||||||
|
gap: '8px',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
});
|
||||||
|
const ok = document.createElement('button');
|
||||||
|
ok.textContent = 'OK';
|
||||||
|
Object.assign(ok.style, {
|
||||||
|
padding: '6px 14px',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
background: '#1f883d',
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: '13px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
});
|
||||||
|
const cancel = document.createElement('button');
|
||||||
|
cancel.textContent = 'Cancel';
|
||||||
|
Object.assign(cancel.style, {
|
||||||
|
padding: '6px 14px',
|
||||||
|
border: '1px solid #d0d7de',
|
||||||
|
borderRadius: '4px',
|
||||||
|
background: '#f6f8fa',
|
||||||
|
color: '#1f2328',
|
||||||
|
fontSize: '13px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
});
|
||||||
|
buttons.appendChild(cancel);
|
||||||
|
buttons.appendChild(ok);
|
||||||
|
box.appendChild(buttons);
|
||||||
|
overlay.appendChild(box);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
let resolved = false;
|
||||||
|
const cleanup = (val) => {
|
||||||
|
if (resolved) return;
|
||||||
|
resolved = true;
|
||||||
|
document.body.removeChild(overlay);
|
||||||
|
resolve(val);
|
||||||
|
};
|
||||||
|
ok.addEventListener('click', () => cleanup(input.value || null));
|
||||||
|
cancel.addEventListener('click', () => cleanup(null));
|
||||||
|
overlay.addEventListener('click', (ev) => {
|
||||||
|
if (ev.target === overlay) cleanup(null);
|
||||||
|
});
|
||||||
|
input.addEventListener('keydown', (kev) => {
|
||||||
|
if (kev.key === 'Enter') cleanup(input.value || null);
|
||||||
|
if (kev.key === 'Escape') cleanup(null);
|
||||||
|
});
|
||||||
|
setTimeout(() => input.focus(), 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmInline({ title, message, danger = false }) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
Object.assign(overlay.style, {
|
||||||
|
position: 'fixed',
|
||||||
|
inset: '0',
|
||||||
|
background: 'rgba(0,0,0,0.45)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: 99999,
|
||||||
|
});
|
||||||
|
const box = document.createElement('div');
|
||||||
|
Object.assign(box.style, {
|
||||||
|
background: '#ffffff',
|
||||||
|
color: '#1f2328',
|
||||||
|
border: '1px solid #d0d7de',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '20px 24px',
|
||||||
|
minWidth: '320px',
|
||||||
|
maxWidth: '480px',
|
||||||
|
boxShadow: '0 12px 32px rgba(0,0,0,0.25)',
|
||||||
|
fontFamily: 'system-ui, sans-serif',
|
||||||
|
});
|
||||||
|
if (title) {
|
||||||
|
const h = document.createElement('div');
|
||||||
|
h.textContent = title;
|
||||||
|
Object.assign(h.style, { fontSize: '14px', fontWeight: '600', marginBottom: '12px' });
|
||||||
|
box.appendChild(h);
|
||||||
|
}
|
||||||
|
if (message) {
|
||||||
|
const m = document.createElement('div');
|
||||||
|
m.textContent = message;
|
||||||
|
Object.assign(m.style, {
|
||||||
|
fontSize: '13px',
|
||||||
|
color: '#1f2328',
|
||||||
|
marginBottom: '14px',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
});
|
||||||
|
box.appendChild(m);
|
||||||
|
}
|
||||||
|
const buttons = document.createElement('div');
|
||||||
|
Object.assign(buttons.style, { display: 'flex', gap: '8px', justifyContent: 'flex-end' });
|
||||||
|
const ok = document.createElement('button');
|
||||||
|
ok.textContent = danger ? 'Delete' : 'OK';
|
||||||
|
Object.assign(ok.style, {
|
||||||
|
padding: '6px 14px',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
background: danger ? '#cf222e' : '#1f883d',
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: '13px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
});
|
||||||
|
const cancel = document.createElement('button');
|
||||||
|
cancel.textContent = 'Cancel';
|
||||||
|
Object.assign(cancel.style, {
|
||||||
|
padding: '6px 14px',
|
||||||
|
border: '1px solid #d0d7de',
|
||||||
|
borderRadius: '4px',
|
||||||
|
background: '#f6f8fa',
|
||||||
|
color: '#1f2328',
|
||||||
|
fontSize: '13px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
});
|
||||||
|
buttons.appendChild(cancel);
|
||||||
|
buttons.appendChild(ok);
|
||||||
|
box.appendChild(buttons);
|
||||||
|
overlay.appendChild(box);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
let resolved = false;
|
||||||
|
const cleanup = (val) => {
|
||||||
|
if (resolved) return;
|
||||||
|
resolved = true;
|
||||||
|
document.body.removeChild(overlay);
|
||||||
|
resolve(val);
|
||||||
|
};
|
||||||
|
ok.addEventListener('click', () => cleanup(true));
|
||||||
|
cancel.addEventListener('click', () => cleanup(false));
|
||||||
|
overlay.addEventListener('click', (ev) => {
|
||||||
|
if (ev.target === overlay) cleanup(false);
|
||||||
|
});
|
||||||
|
document.addEventListener('keydown', function onKey(ev) {
|
||||||
|
if (ev.key === 'Enter') {
|
||||||
|
cleanup(true);
|
||||||
|
document.removeEventListener('keydown', onKey);
|
||||||
|
}
|
||||||
|
if (ev.key === 'Escape') {
|
||||||
|
cleanup(false);
|
||||||
|
document.removeEventListener('keydown', onKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
// Resolve the userData path ONCE on mount. The persistence path is
|
// Resolve the userData path ONCE on mount. The persistence path is
|
||||||
// interpolated into a string on every read/write; calling the async
|
// interpolated into a string on every read/write; calling the async
|
||||||
@@ -836,14 +1053,22 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
_canvas = createCanvas(els.canvasHost, _store, {
|
_canvas = createCanvas(els.canvasHost, _store, {
|
||||||
onEdgeClick: (edgeId) => {
|
onEdgeClick: async (edgeId) => {
|
||||||
const edge = _store.getGraph().edges.find((e) => e.id === edgeId);
|
const edge = _store.getGraph().edges.find((e) => e.id === edgeId);
|
||||||
if (!edge) return;
|
if (!edge) return;
|
||||||
const nextKind = window.prompt('Edge kind (solid, dotted, thick):', edge.kind);
|
const nextKind = await promptInline({
|
||||||
|
title: 'Edge kind',
|
||||||
|
message: 'Enter the new edge kind for this connection.',
|
||||||
|
defaultValue: edge.kind,
|
||||||
|
});
|
||||||
if (nextKind && ['solid', 'dotted', 'thick'].includes(nextKind)) {
|
if (nextKind && ['solid', 'dotted', 'thick'].includes(nextKind)) {
|
||||||
_store.setEdgeKind(edgeId, nextKind);
|
_store.setEdgeKind(edgeId, nextKind);
|
||||||
}
|
}
|
||||||
const nextLabel = window.prompt('Edge label (empty to clear):', edge.label || '');
|
const nextLabel = await promptInline({
|
||||||
|
title: 'Edge label',
|
||||||
|
message: 'Enter a label for this edge (leave blank to clear).',
|
||||||
|
defaultValue: edge.label || '',
|
||||||
|
});
|
||||||
if (nextLabel !== null) {
|
if (nextLabel !== null) {
|
||||||
_store.setEdgeLabel(edgeId, nextLabel);
|
_store.setEdgeLabel(edgeId, nextLabel);
|
||||||
}
|
}
|
||||||
@@ -852,10 +1077,11 @@
|
|||||||
// Canvas already paints the .selected highlight; nothing else
|
// Canvas already paints the .selected highlight; nothing else
|
||||||
// needed here for selection state.
|
// needed here for selection state.
|
||||||
},
|
},
|
||||||
onShapeMenu: (nodeId) => {
|
onShapeMenu: async (nodeId) => {
|
||||||
const next = window.prompt(
|
const next = await promptInline({
|
||||||
'New shape (process, decision, terminator, subroutine, document):'
|
title: 'Change shape',
|
||||||
);
|
message: 'New shape (process, decision, terminator, subroutine, document):',
|
||||||
|
});
|
||||||
if (next) _store.setNodeKind(nodeId, next);
|
if (next) _store.setNodeKind(nodeId, next);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -884,9 +1110,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (els.btnReset) {
|
if (els.btnReset) {
|
||||||
els.btnReset.addEventListener('click', () => {
|
els.btnReset.addEventListener('click', async () => {
|
||||||
if (!_store) return;
|
if (!_store) return;
|
||||||
if (!window.confirm('Clear all nodes and edges?')) return;
|
const ok = await confirmInline({
|
||||||
|
title: 'Reset diagram',
|
||||||
|
message: 'Clear all nodes and edges? This cannot be undone.',
|
||||||
|
danger: true,
|
||||||
|
});
|
||||||
|
if (!ok) return;
|
||||||
_store.deserialize({ nodes: [], edges: [] });
|
_store.deserialize({ nodes: [], edges: [] });
|
||||||
setStatus('Reset');
|
setStatus('Reset');
|
||||||
});
|
});
|
||||||
@@ -945,6 +1176,11 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Expose the inline modal helpers (v4.9.9) so jsdom tests can drive them
|
||||||
|
// directly without rebuilding the bundle's IIFE. Production code accesses
|
||||||
|
// these by closure; this handle exists purely for unit tests.
|
||||||
|
window.FlowchartModals = { promptInline, confirmInline };
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', bootstrap);
|
document.addEventListener('DOMContentLoaded', bootstrap);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ afterEach(() => {
|
|||||||
delete window.FlowchartMermaid;
|
delete window.FlowchartMermaid;
|
||||||
delete window.FlowchartStore;
|
delete window.FlowchartStore;
|
||||||
delete window.FlowchartCanvas;
|
delete window.FlowchartCanvas;
|
||||||
|
delete window.FlowchartModals;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('flowchart-generator.html — stylesheet links', () => {
|
describe('flowchart-generator.html — stylesheet links', () => {
|
||||||
@@ -234,3 +235,127 @@ describe('flowchart pure modules — UMD browser-global assignment (v4.9.7)', ()
|
|||||||
expect(src).toMatch(new RegExp(`window\\.${globalName}\\s*=\\s*exported`));
|
expect(src).toMatch(new RegExp(`window\\.${globalName}\\s*=\\s*exported`));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// v4.9.9 — Electron renderer contexts disable `window.prompt` and
|
||||||
|
// `window.confirm` (the BrowserWindow returns undefined when called),
|
||||||
|
// which broke shape change / edge kind / edge label / reset confirm in
|
||||||
|
// the standalone Flowchart Generator window. The bundle now ships
|
||||||
|
// `promptInline` and `confirmInline` (custom DOM overlay modals) and
|
||||||
|
// exposes them as `window.FlowchartModals` for testing. These tests load
|
||||||
|
// the real bundle into jsdom so we exercise the actual overlay code.
|
||||||
|
describe('flowchart-bundle: inline modal helpers (v4.9.9)', () => {
|
||||||
|
const BUNDLE_PATH = path.join(__dirname, '..', 'src', 'renderer', 'flowchart-bundle.js');
|
||||||
|
const HTML_PATH_BUNDLE = path.join(__dirname, '..', 'src', 'flowchart-generator.html');
|
||||||
|
|
||||||
|
async function loadBundle() {
|
||||||
|
const html = fs.readFileSync(HTML_PATH_BUNDLE, 'utf-8');
|
||||||
|
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
||||||
|
document.body.innerHTML = bodyMatch ? bodyMatch[1] : html;
|
||||||
|
window.electronAPI = {
|
||||||
|
flowchart: {
|
||||||
|
getUserDataPath: jest.fn(async () => '/userdata'),
|
||||||
|
readFile: jest.fn(async () => null),
|
||||||
|
writeFile: jest.fn(async () => undefined),
|
||||||
|
insertAtCursor: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const bundleSrc = fs.readFileSync(BUNDLE_PATH, 'utf-8');
|
||||||
|
// eslint-disable-next-line no-new-func
|
||||||
|
new Function('window', 'document', bundleSrc)(window, document);
|
||||||
|
// Flush microtasks so the bootstrap chain settles before tests run.
|
||||||
|
for (let i = 0; i < 5; i += 1) {
|
||||||
|
await Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('promptInline resolves with the entered value on OK click', async () => {
|
||||||
|
await loadBundle();
|
||||||
|
expect(typeof window.FlowchartModals.promptInline).toBe('function');
|
||||||
|
const promise = window.FlowchartModals.promptInline({
|
||||||
|
title: 'Edge kind',
|
||||||
|
message: 'Enter the new edge kind.',
|
||||||
|
defaultValue: 'solid',
|
||||||
|
});
|
||||||
|
const input = document.querySelector('input');
|
||||||
|
expect(input).not.toBeNull();
|
||||||
|
input.value = 'dotted';
|
||||||
|
const okButton = Array.from(document.querySelectorAll('button')).find(
|
||||||
|
(b) => b.textContent === 'OK'
|
||||||
|
);
|
||||||
|
expect(okButton).toBeDefined();
|
||||||
|
okButton.click();
|
||||||
|
await expect(promise).resolves.toBe('dotted');
|
||||||
|
expect(document.querySelector('input')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('promptInline resolves null on Cancel click', async () => {
|
||||||
|
await loadBundle();
|
||||||
|
const promise = window.FlowchartModals.promptInline({
|
||||||
|
title: 'Edge label',
|
||||||
|
message: 'Enter a label.',
|
||||||
|
defaultValue: '',
|
||||||
|
});
|
||||||
|
const cancelButton = Array.from(document.querySelectorAll('button')).find(
|
||||||
|
(b) => b.textContent === 'Cancel'
|
||||||
|
);
|
||||||
|
expect(cancelButton).toBeDefined();
|
||||||
|
cancelButton.click();
|
||||||
|
await expect(promise).resolves.toBeNull();
|
||||||
|
expect(document.querySelector('input')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('promptInline resolves null on Escape key', async () => {
|
||||||
|
await loadBundle();
|
||||||
|
const promise = window.FlowchartModals.promptInline({
|
||||||
|
title: 'Edge kind',
|
||||||
|
defaultValue: 'solid',
|
||||||
|
});
|
||||||
|
const input = document.querySelector('input');
|
||||||
|
const ev = new window.KeyboardEvent('keydown', { key: 'Escape', bubbles: true });
|
||||||
|
input.dispatchEvent(ev);
|
||||||
|
await expect(promise).resolves.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirmInline resolves true on OK click', async () => {
|
||||||
|
await loadBundle();
|
||||||
|
const promise = window.FlowchartModals.confirmInline({
|
||||||
|
title: 'Reset diagram',
|
||||||
|
message: 'Clear all nodes and edges?',
|
||||||
|
});
|
||||||
|
const okButton = Array.from(document.querySelectorAll('button')).find(
|
||||||
|
(b) => b.textContent === 'OK'
|
||||||
|
);
|
||||||
|
expect(okButton).toBeDefined();
|
||||||
|
okButton.click();
|
||||||
|
await expect(promise).resolves.toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirmInline resolves false on Cancel click', async () => {
|
||||||
|
await loadBundle();
|
||||||
|
const promise = window.FlowchartModals.confirmInline({
|
||||||
|
title: 'Reset diagram',
|
||||||
|
message: 'Clear all nodes and edges?',
|
||||||
|
});
|
||||||
|
const cancelButton = Array.from(document.querySelectorAll('button')).find(
|
||||||
|
(b) => b.textContent === 'Cancel'
|
||||||
|
);
|
||||||
|
expect(cancelButton).toBeDefined();
|
||||||
|
cancelButton.click();
|
||||||
|
await expect(promise).resolves.toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirmInline with danger flag renders a Delete primary button', async () => {
|
||||||
|
await loadBundle();
|
||||||
|
const promise = window.FlowchartModals.confirmInline({
|
||||||
|
title: 'Reset diagram',
|
||||||
|
message: 'Clear all nodes and edges?',
|
||||||
|
danger: true,
|
||||||
|
});
|
||||||
|
const buttons = Array.from(document.querySelectorAll('button'));
|
||||||
|
const primary = buttons.find((b) => b.textContent === 'Delete');
|
||||||
|
expect(primary).toBeDefined();
|
||||||
|
expect(primary.style.background).toBe('rgb(207, 34, 46)');
|
||||||
|
primary.click();
|
||||||
|
await expect(promise).resolves.toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user