diff --git a/packages/mindplot/cypress/integration/playground.test.js b/packages/mindplot/cypress/integration/playground.test.js index 0538110e..4f784102 100644 --- a/packages/mindplot/cypress/integration/playground.test.js +++ b/packages/mindplot/cypress/integration/playground.test.js @@ -10,18 +10,19 @@ context('Playground', () => { }); it('the playground viewmode.html page should match its snapshot', () => { cy.visit('/viewmode.html'); + cy.get('#mindplot.ready').should('exist'); cy.matchImageSnapshot('viewmode'); }); it('the playground container.html page should match its snapshot', () => { cy.visit('/container.html'); - // TODO: wait for mind map to load instead of an arbitrary number of ms - cy.wait(5000); + cy.getIframeBody() + .find('#mindplot.ready') + .should('exist'); cy.matchImageSnapshot('container'); }); it('the playground editor.html page should match its snapshot', () => { cy.visit('/editor.html'); - // TODO: wait for mind map to load instead of an arbitrary number of ms - cy.wait(5000); + cy.get('#mindplot.ready').should('exist'); // TODO: why is the editor appearing twice in the snapshot? cy.matchImageSnapshot('editor'); }); diff --git a/packages/mindplot/cypress/snapshots/playground.test.js/viewmode.snap.png b/packages/mindplot/cypress/snapshots/playground.test.js/viewmode.snap.png index 8f2d685a..a58c473d 100644 Binary files a/packages/mindplot/cypress/snapshots/playground.test.js/viewmode.snap.png and b/packages/mindplot/cypress/snapshots/playground.test.js/viewmode.snap.png differ diff --git a/packages/mindplot/cypress/support/commands.js b/packages/mindplot/cypress/support/commands.js index cf3e92fd..bcb624c8 100644 --- a/packages/mindplot/cypress/support/commands.js +++ b/packages/mindplot/cypress/support/commands.js @@ -13,3 +13,9 @@ if (Cypress.env('imageSnaphots')) { () => Promise.resolve(), ); } + +// https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/ +Cypress.Commands.add('getIframeBody', () => cy + .get('iframe') + .its('0.contentDocument.body').should('not.be.empty') + .then(cy.wrap)); diff --git a/packages/mindplot/test/playground/map-render/js/editor.js b/packages/mindplot/test/playground/map-render/js/editor.js index c340ff02..d9ecf282 100644 --- a/packages/mindplot/test/playground/map-render/js/editor.js +++ b/packages/mindplot/test/playground/map-render/js/editor.js @@ -7,6 +7,10 @@ const example = async () => { const options = await loadDesignerOptions(); const designer = buildDesigner(options); + designer.addEvent('loadSuccess', () => { + document.getElementById('mindplot').classList.add('ready'); + }); + // Load map from XML file persisted on disk... const persistence = PersistenceManager.getInstance(); const mindmap = persistence.load(mapId); diff --git a/packages/mindplot/test/playground/map-render/js/embedded.js b/packages/mindplot/test/playground/map-render/js/embedded.js index 431349af..dfe3967d 100644 --- a/packages/mindplot/test/playground/map-render/js/embedded.js +++ b/packages/mindplot/test/playground/map-render/js/embedded.js @@ -10,6 +10,9 @@ const example = async () => { const options = await loadDesignerOptions(confUrl); const designer = buildDesigner(options); + designer.addEvent('loadSuccess', () => { + document.getElementById('mindplot').classList.add('ready'); + }); // Load map from XML file persisted on disk... const persistence = PersistenceManager.getInstance(); let mindmap; diff --git a/packages/mindplot/test/playground/map-render/js/viewmode.js b/packages/mindplot/test/playground/map-render/js/viewmode.js index 3cb4c06e..043836c2 100644 --- a/packages/mindplot/test/playground/map-render/js/viewmode.js +++ b/packages/mindplot/test/playground/map-render/js/viewmode.js @@ -8,7 +8,9 @@ const example = async () => { const options = await loadDesignerOptions(); options.readOnly = true; const designer = buildDesigner(options); - + designer.addEvent('loadSuccess', () => { + document.getElementById('mindplot').classList.add('ready'); + }); // Load map from XML file persisted on disk... const persistence = PersistenceManager.getInstance(); let mindmap; diff --git a/packages/web2d/src/components/peer/svg/TextPeer.js b/packages/web2d/src/components/peer/svg/TextPeer.js index fe5ee45f..e652df63 100644 --- a/packages/web2d/src/components/peer/svg/TextPeer.js +++ b/packages/web2d/src/components/peer/svg/TextPeer.js @@ -17,6 +17,7 @@ */ import { $defined } from '@wisemapping/core-js'; import ElementPeer from './ElementPeer'; +import { getPosition } from '../utils/DomUtils'; class TextPeer extends ElementPeer { constructor(Font) { @@ -80,13 +81,7 @@ class TextPeer extends ElementPeer { } getNativePosition() { - const computedStyles = window.getComputedStyle(this._native); - const marginTop = computedStyles.getPropertyValue('margin-top') || 0; - const marginLeft = computedStyles.getPropertyValue('margin-left') || 0; - return { - top: this._native.offsetTop - parseFloat(marginTop), - left: this._native.offsetLeft - parseFloat(marginLeft), - }; + return getPosition(this._native); } setFont(font, size, style, weight) { diff --git a/packages/web2d/src/components/peer/utils/DomUtils.js b/packages/web2d/src/components/peer/utils/DomUtils.js new file mode 100644 index 00000000..1180f76f --- /dev/null +++ b/packages/web2d/src/components/peer/utils/DomUtils.js @@ -0,0 +1,62 @@ +/* eslint-disable import/prefer-default-export */ + +// quick hand-made version of $.css() +export const getStyle = (elem, prop) => { + const result = window.getComputedStyle(elem)[prop]; + if (typeof result === 'string' && /px$/.test(result)) { + return parseFloat(result); + } + return result; +}; + +// offset and position utils extracted and adapted from jquery source +// https://github.com/jquery/jquery/blob/main/src/offset.js +export const getOffset = (elem) => { + if (!elem || !elem.getClientRects().length) { + return { top: 0, left: 0 }; + } + // Get document-relative position by adding viewport scroll to viewport-relative gBCR + const rect = elem.getBoundingClientRect(); + const win = elem.ownerDocument.defaultView; + return { + top: rect.top + win.pageYOffset, + left: rect.left + win.pageXOffset, + }; +}; + +export const getPosition = (elem) => { + let offsetParent; + let offset; + let doc; + let parentOffset = { top: 0, left: 0 }; + + // position:fixed elements are offset from the viewport, which itself always has zero offset + if (getStyle(elem, 'position') === 'fixed') { + // Assume position:fixed implies availability of getBoundingClientRect + offset = elem.getBoundingClientRect(); + } else { + offset = getOffset(elem); + + // Account for the *real* offset parent, which can be the document or its root element + // when a statically positioned element is identified + doc = elem.ownerDocument; + offsetParent = elem.offsetParent || doc.documentElement; + while (offsetParent + && (offsetParent === doc.body || offsetParent === doc.documentElement) + && getStyle(offsetParent, 'position') === 'static') { + offsetParent = offsetParent.parentNode; + } + if (offsetParent && offsetParent !== elem && offsetParent.nodeType === 1) { + // Incorporate borders into its offset, since they are outside its content origin + parentOffset = getOffset(offsetParent); + parentOffset.top += getStyle(offsetParent, 'borderTopWidth'); + parentOffset.left += getStyle(offsetParent, 'borderLeftWidth'); + } + } + + // Subtract parent offsets and element margins + return { + top: offset.top - parentOffset.top - getStyle(elem, 'marginTop'), + left: offset.left - parentOffset.left - getStyle(elem, 'marginLeft'), + }; +};