Add automation

This commit is contained in:
Ezequiel Vega 2022-01-26 19:25:11 +00:00 committed by Paulo Veiga
parent 4d1eb974da
commit 84c623f798
65 changed files with 1344 additions and 2067 deletions

2
.gitignore vendored
View File

@ -50,3 +50,5 @@ Thumbs.db
**/build/**/* **/build/**/*
.vscode .vscode
*/test/playground/dist

View File

@ -17,7 +17,6 @@ pipelines:
- cypress - cypress
script: script:
- export CYPRESS_imageSnaphots="true" - export CYPRESS_imageSnaphots="true"
- yarn install
- yarn bootstrap - yarn bootstrap
- yarn build - yarn build
- yarn lint - yarn lint

View File

@ -3,7 +3,7 @@ services:
e2e: e2e:
image: cypress/included:8.4.1 image: cypress/included:8.4.1
container_name: wisemapping-integration-tests container_name: wisemapping-integration-tests
entrypoint: '/bin/sh -c "yarn bootstrap && yarn test:integration"' entrypoint: '/bin/sh -c "yarn bootstrap && yarn build && yarn test:integration"'
working_dir: /e2e working_dir: /e2e
environment: environment:
- CYPRESS_imageSnaphots=true - CYPRESS_imageSnaphots=true

View File

@ -3,7 +3,7 @@ services:
e2e: e2e:
image: cypress/included:8.4.1 image: cypress/included:8.4.1
container_name: wisemapping-integration-tests container_name: wisemapping-integration-tests
entrypoint: '/bin/sh -c "yarn bootstrap && yarn test:integration"' entrypoint: '/bin/sh -c "yarn bootstrap && yarn build && yarn test:integration"'
working_dir: /e2e working_dir: /e2e
environment: environment:
- CYPRESS_imageSnaphots=true - CYPRESS_imageSnaphots=true

View File

@ -16,8 +16,6 @@
* limitations under the License. * limitations under the License.
*/ */
/** /**
* Cross-browser implementation of creating an XML document object. * Cross-browser implementation of creating an XML document object.
*/ */
@ -48,7 +46,7 @@ export const createDocument = function () {
obj - object to inspect obj - object to inspect
*/ */
export const $defined = function (obj) { export const $defined = function (obj) {
return obj != undefined; return obj != undefined;
}; };

View File

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@ -0,0 +1,16 @@
context('Relationship Topics', () => {
beforeEach(() => {
cy.visit('/editor.html');
cy.reload();
cy.get('[test-id="30-11-relationship"]').click({ force: true });
});
it('Change shape relationship', () => {
cy.get('[test-id="control-56"]').trigger('mousedown', { force: true });
cy.get('body').trigger('mousemove', { clientX: 500, clientY: 200 });
cy.get('body').trigger('mouseup');
cy.matchImageSnapshot('changeShapeRealtionship');
cy.get('[test-id="control-56"]').invoke('attr', 'cy').should('eq', '-131.75');
});
});

View File

@ -0,0 +1,67 @@
context('Edit Topic', () => {
// TODO: review why click({force: true}) is needed in these tests
// also, why is the element outside the viewport in screenshots?
beforeEach(() => {
cy.visit('/editor.html');
cy.reload();
cy.get('[test-id=1]').click();
});
it('Change Main Topic Text', () => {
cy.get('body').type('New Title Main Topic{enter}');
cy.get('[test-id=1] > text > tspan').should('have.text', 'New Title Main Topic');
cy.matchImageSnapshot('changeMainTopicText');
});
it('Change Font Size', () => {
cy.get('#fontSizeTip').click();
cy.get('.popover #small').click();
cy.get('[test-id=1] > text').invoke('attr', 'font-size').should('eq', '8.0625');
cy.matchImageSnapshot('changeFontSizeSmall');
cy.get('#fontSizeTip').click();
cy.get('.popover #normal').click({ force: true });
cy.get('[test-id=1] > text').invoke('attr', 'font-size').should('eq', '10.75');
cy.matchImageSnapshot('changeFontSizeNormal');
cy.get('#fontSizeTip').click();
cy.get('.popover #large').click({ force: true });
cy.get('[test-id=1] > text').invoke('attr', 'font-size').should('eq', '13.4375');
cy.matchImageSnapshot('changeFontSizeLarge');
cy.get('#fontSizeTip').click();
cy.get('.popover #huge').click({ force: true });
cy.get('[test-id=1] > text').invoke('attr', 'font-size').should('eq', '20.15625');
cy.matchImageSnapshot('changeFontSizeHuge');
});
it('Change Font type', () => {
cy.get('#fontFamilyTip').click();
cy.get('[model="Times"]').click({ force: true });
cy.get('[test-id=1] > text').invoke('attr', 'font-family').should('eq', 'Times');
cy.matchImageSnapshot('changeFontType');
});
it('Change Font Italic', () => {
cy.get('#fontItalicTip').click();
cy.get('[test-id=1] > text').invoke('attr', 'font-style').should('eq', 'italic');
cy.matchImageSnapshot('changeFontItalic');
});
it('Change Font color', () => {
cy.get('#fontColorTip').click();
cy.get('[title="RGB (153, 0, 255)"]').click({ force: true });
cy.get('[test-id=1] > text').invoke('attr', 'fill').should('eq', 'rgb(153, 0, 255)');
cy.matchImageSnapshot('changeFontColor');
});
});

View File

@ -0,0 +1,49 @@
context('Node manager', () => {
before(() => {
cy.visit('/editor.html');
});
it('shortcut add sibling node', () => {
cy.contains('Mind Mapping').click();
cy.get('body').type('{enter}').type('Mind Mapping rocks!!').type('{enter}');
cy.get('[test-id=36] > text > tspan').should('exist');
cy.matchImageSnapshot('editor-shortcut-edit');
});
it('shortcut add child node', () => {
cy.contains('Mind Mapping rocks!!').click();
cy.get('body').type('{insert}').type('Child 1 mind Mapping rocks!!').type('{enter}');
cy.get('body').type('{enter}').type('Child 2 mind Mapping rocks!!').type('{enter}');
cy.get('[test-id=36] > text > tspan').should('exist');
cy.get('[test-id=37] > text > tspan').should('exist');
cy.matchImageSnapshot('addChildNodeSortcut');
});
it('Delete topic', () => {
cy.get('[test-id=37]').click();
cy.get('body').type('{del}');
cy.get('[test-id=37]').should('not.exist');
cy.matchImageSnapshot('deleteTopicShortcut');
});
it('undo changes', () => {
cy.get('#undoEditionTip').click();
cy.get('[test-id=36] > text > tspan').should('exist');
cy.matchImageSnapshot('undoChange');
});
it('Save changes', () => {
cy.contains('Mind Mapping rocks!!').click();
cy.get('body').type('{ctrl}s');
cy.matchImageSnapshot('saveChagesShortcut');
});
});

View File

@ -0,0 +1,35 @@
context('Change topic position', () => {
beforeEach(() => {
cy.visit('/editor.html');
cy.reload();
});
it('Move up node "Mind Mapping"', () => {
const position = { clientX: 270, clientY: 160 };
cy.contains('Mind Mapping').trigger('mousedown');
cy.get('body').trigger('mousemove', position);
cy.get('body').trigger('mouseup');
cy.matchImageSnapshot('moveupNode');
});
it('Move down node "Mind Mapping"', () => {
cy.contains('Mind Mapping').trigger('mousedown');
cy.get('body').trigger('mousemove', { clientX: 350, clientY: 380 });
cy.get('body').trigger('mouseup');
cy.matchImageSnapshot('movedownNode');
});
it('Move default position node "Mind Mapping"', () => {
cy.contains('Mind Mapping').trigger('mousedown');
cy.get('body').trigger('mousemove', { clientX: 270, clientY: 240 });
cy.get('body').trigger('mouseup');
cy.matchImageSnapshot('moveDefaultPosition');
});
it('Move left node "Mind Mapping"', () => {
cy.contains('Mind Mapping').trigger('mousedown');
cy.get('body').trigger('mousemove', { clientX: 700, clientY: 240 });
cy.get('body').trigger('mouseup');
cy.matchImageSnapshot('moveleftNode');
});
});

View File

@ -0,0 +1,44 @@
context('Change Topic shape', () => {
beforeEach(() => {
cy.visit('/editor.html');
cy.reload();
cy.contains('Try it Now!').click();
});
it('change to square shape', () => {
cy.get('#topicShapeTip').click();
cy.get('#rectagle').click();
cy.get('[test-id=11] > rect').eq(1).invoke('attr', 'rx').should('eq', '0');
cy.matchImageSnapshot('changeToSquareShape');
});
it('change to rounded rectagle', () => {
cy.get('#topicShapeTip').click();
// TODO: The parameter {force: true} was placed because it does not detect that the element is visible
cy.get('#rounded_rectagle').click({ force: true });
cy.get('[test-id=11] > rect').eq(1).invoke('attr', 'rx').should('eq', '4.05');
cy.matchImageSnapshot('changeToRoundedRectagle');
});
it('change to line', () => {
cy.get('#topicShapeTip').click();
// TODO: The parameter {force: true} was placed because it does not detect that the element is visible
cy.get('#line').click({ force: true });
cy.matchImageSnapshot('changeToLine');
});
it('change to elipse shape', () => {
cy.get('#topicShapeTip').click();
// TODO: The parameter {force: true} was placed because it does not detect that the element is visible
cy.get('#elipse').click({ force: true });
cy.get('[test-id=11] > rect').eq(1).invoke('attr', 'rx').should('eq', '12.15');
cy.matchImageSnapshot('changeToElipseShape');
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@ -8,13 +8,12 @@ const { merge } = require('webpack-merge');
const playgroundConfig = { const playgroundConfig = {
mode: 'development', mode: 'development',
entry: { entry: {
"editor.bundle": path.join(__dirname, 'src', 'index.tsx'),
viewmode: path.resolve(__dirname, './test/playground/map-render/js/viewmode'), viewmode: path.resolve(__dirname, './test/playground/map-render/js/viewmode'),
embedded: path.resolve(__dirname, './test/playground/map-render/js/embedded'), embedded: path.resolve(__dirname, './test/playground/map-render/js/embedded'),
editor: path.resolve(__dirname, './test/playground/map-render/js/editor'), editor: path.resolve(__dirname, './test/playground/map-render/js/editor'),
}, },
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'test/playground/dist'),
filename: '[name].js', filename: '[name].js',
library: { library: {
type: 'umd', type: 'umd',
@ -24,7 +23,6 @@ const playgroundConfig = {
historyApiFallback: true, historyApiFallback: true,
port: 8081, port: 8081,
open: false, open: false,
writeToDisk: true,
}, },
module: { module: {
rules: [ rules: [

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

@ -34,49 +34,49 @@ abstract class ActionDispatcher extends Events {
super(); super();
} }
abstract addRelationship(model: RelationshipModel, mindmap: Mindmap); abstract addRelationship(model: RelationshipModel, mindmap: Mindmap);
abstract addTopics(models: NodeModel[], parentTopicId: any[]); abstract addTopics(models: NodeModel[], parentTopicId: any[]);
abstract deleteEntities(topicsIds: number[], relIds: number[]); abstract deleteEntities(topicsIds: number[], relIds: number[]);
abstract dragTopic(topicId: number, position: Point, order: number, parentTopic: Topic); abstract dragTopic(topicId: number, position: Point, order: number, parentTopic: Topic);
abstract moveTopic(topicId: number, position: any); abstract moveTopic(topicId: number, position: any);
abstract moveControlPoint(ctrlPoint: this, point: any); abstract moveControlPoint(ctrlPoint: this, point: any);
abstract changeFontFamilyToTopic(topicIds: number[], fontFamily: string); abstract changeFontFamilyToTopic(topicIds: number[], fontFamily: string);
abstract changeFontStyleToTopic(topicsIds: number[]); abstract changeFontStyleToTopic(topicsIds: number[]);
abstract changeFontColorToTopic(topicsIds: number[], color: string); abstract changeFontColorToTopic(topicsIds: number[], color: string);
abstract changeFontSizeToTopic(topicsIds: number[], size: number); abstract changeFontSizeToTopic(topicsIds: number[], size: number);
abstract changeBackgroundColorToTopic(topicsIds: number[], color: string); abstract changeBackgroundColorToTopic(topicsIds: number[], color: string);
abstract changeBorderColorToTopic(topicsIds: number[], color: string); abstract changeBorderColorToTopic(topicsIds: number[], color: string);
abstract changeShapeTypeToTopic(topicsIds: number[], shapeType: string); abstract changeShapeTypeToTopic(topicsIds: number[], shapeType: string);
abstract changeFontWeightToTopic(topicsIds: number[]); abstract changeFontWeightToTopic(topicsIds: number[]);
abstract changeTextToTopic(topicsIds: number[], text: string); abstract changeTextToTopic(topicsIds: number[], text: string);
abstract shrinkBranch(topicsIds: number[], collapse: boolean); abstract shrinkBranch(topicsIds: number[], collapse: boolean);
abstract addFeatureToTopic(topicId: number, type: string, attributes: object); abstract addFeatureToTopic(topicId: number, type: string, attributes: object);
abstract changeFeatureToTopic(topicId: number, featureId: any, attributes: object); abstract changeFeatureToTopic(topicId: number, featureId: any, attributes: object);
abstract removeFeatureFromTopic(topicId: number, featureId: number); abstract removeFeatureFromTopic(topicId: number, featureId: number);
static setInstance = (dispatcher: ActionDispatcher) => { static setInstance = (dispatcher: ActionDispatcher) => {
this._instance = dispatcher; this._instance = dispatcher;
}; };
static getInstance = (): ActionDispatcher => ActionDispatcher._instance; static getInstance = (): ActionDispatcher => ActionDispatcher._instance;
} }
export default ActionDispatcher; export default ActionDispatcher;

View File

@ -23,25 +23,25 @@ import ActionDispatcher from './ActionDispatcher';
class ControlPoint { class ControlPoint {
constructor() { constructor() {
const control1 = new Elipse({ this.control1 = new Elipse({
width: 6, width: 6,
height: 6, height: 6,
stroke: '1 solid #6589de', stroke: '1 solid #6589de',
fillColor: 'gray', fillColor: 'gray',
visibility: false, visibility: false,
}); });
control1.setCursor('pointer'); this.control1.setCursor('pointer');
const control2 = new Elipse({ this.control2 = new Elipse({
width: 6, width: 6,
height: 6, height: 6,
stroke: '1 solid #6589de', stroke: '1 solid #6589de',
fillColor: 'gray', fillColor: 'gray',
visibility: false, visibility: false,
}); });
control2.setCursor('pointer'); this.control2.setCursor('pointer');
this._controlPointsController = [control1, control2]; this._controlPointsController = [this.control1, this.control2];
this._controlLines = [ this._controlLines = [
new Line({ strokeColor: '#6589de', strokeWidth: 1, opacity: 0.3 }), new Line({ strokeColor: '#6589de', strokeWidth: 1, opacity: 0.3 }),
new Line({ strokeColor: '#6589de', strokeWidth: 1, opacity: 0.3 }), new Line({ strokeColor: '#6589de', strokeWidth: 1, opacity: 0.3 }),
@ -84,6 +84,11 @@ class ControlPoint {
this._endPoint[1] = { ...this._line.getLine().getTo() }; this._endPoint[1] = { ...this._line.getLine().getTo() };
} }
setControlPointTestId(ctrlPoint1, ctrlPoint2) {
this.control1.setTestId(ctrlPoint1);
this.control2.setTestId(ctrlPoint2);
}
redraw() { redraw() {
if ($defined(this._line)) this._createControlPoint(); if ($defined(this._line)) this._createControlPoint();
} }

View File

@ -56,6 +56,7 @@ class Relationship extends ConnectionLine {
this._line2d.setCursor('pointer'); this._line2d.setCursor('pointer');
this._line2d.setStroke(1, 'solid', strokeColor); this._line2d.setStroke(1, 'solid', strokeColor);
this._line2d.setDashed(4, 2); this._line2d.setDashed(4, 2);
this._line2d.setTestId(`${model.getFromNode()}-${model.getToNode()}-relationship`);
this._focusShape = this._createLine(this.getLineType(), ConnectionLine.SIMPLE_CURVED); this._focusShape = this._createLine(this.getLineType(), ConnectionLine.SIMPLE_CURVED);
this._focusShape.setStroke(2, 'solid', '#3f96ff'); this._focusShape.setStroke(2, 'solid', '#3f96ff');
@ -83,6 +84,12 @@ class Relationship extends ConnectionLine {
if ($defined(model.getSrcCtrlPoint())) { if ($defined(model.getSrcCtrlPoint())) {
const srcPoint = { ...model.getSrcCtrlPoint() }; const srcPoint = { ...model.getSrcCtrlPoint() };
this.setSrcControlPoint(srcPoint); this.setSrcControlPoint(srcPoint);
// Set test id in control point
this._controlPointsController.setControlPointTestId(
`control-${Math.abs(srcPoint.x)}`,
`control-${Math.abs(srcPoint.y)}`,
);
} }
if ($defined(model.getDestCtrlPoint())) { if ($defined(model.getDestCtrlPoint())) {
const destPoint = { ...model.getDestCtrlPoint() }; const destPoint = { ...model.getDestCtrlPoint() };

View File

@ -103,9 +103,9 @@ class RelationshipPivot {
const model = this._designer.getModel(); const model = this._designer.getModel();
const topics = model.getTopics(); const topics = model.getTopics();
topics.forEach(((topic) => { topics.forEach((topic) => {
topic.removeEvent('ontfocus', this._onTopicClick); topic.removeEvent('ontfocus', this._onTopicClick);
})); });
workspace.removeChild(this._pivot); workspace.removeChild(this._pivot);
workspace.removeChild(this._startArrow); workspace.removeChild(this._startArrow);
@ -166,9 +166,7 @@ class RelationshipPivot {
// Avoid circular connections ... // Avoid circular connections ...
if (targetTopic.getId() !== sourceTopic.getId()) { if (targetTopic.getId() !== sourceTopic.getId()) {
const relModel = mindmap.createRelationship(targetTopic.getId(), sourceTopic.getId()); const relModel = mindmap.createRelationship(targetTopic.getId(), sourceTopic.getId());
this._designer this._designer.getActionDispatcher().addRelationship(relModel);
.getActionDispatcher()
.addRelationship(relModel);
} }
this.dispose(); this.dispose();
} }

View File

@ -16,16 +16,9 @@
* limitations under the License. * limitations under the License.
*/ */
import $ from 'jquery'; import $ from 'jquery';
import { $assert, $defined } from '@wisemapping/core-js';
import { import {
$assert, Rect, Image, Line, Text, Group,
$defined,
} from '@wisemapping/core-js';
import {
Rect,
Image,
Line,
Text,
Group,
} from '@wisemapping/web2d'; } from '@wisemapping/web2d';
import NodeGraph from './NodeGraph'; import NodeGraph from './NodeGraph';
@ -41,22 +34,18 @@ import NoteEditor from './widget/NoteEditor';
import ActionDispatcher from './ActionDispatcher'; import ActionDispatcher from './ActionDispatcher';
import LinkEditor from './widget/LinkEditor'; import LinkEditor from './widget/LinkEditor';
import TopicEventDispatcher, { import TopicEventDispatcher, { TopicEvent } from './TopicEventDispatcher';
TopicEvent, import { TopicShape } from './model/INodeModel';
} from './TopicEventDispatcher';
import {
TopicShape,
} from './model/INodeModel';
const ICON_SCALING_FACTOR = 1.3; const ICON_SCALING_FACTOR = 1.3;
class Topic extends NodeGraph { class Topic extends NodeGraph {
/** /**
* @extends mindplot.NodeGraph * @extends mindplot.NodeGraph
* @constructs * @constructs
* @param model * @param model
* @param options * @param options
*/ */
constructor(model, options) { constructor(model, options) {
super(model, options); super(model, options);
this._children = []; this._children = [];
@ -92,9 +81,9 @@ class Topic extends NodeGraph {
} }
/** /**
* @param {String} type the topic shape type * @param {String} type the topic shape type
* @see {@link mindplot.model.INodeModel} * @see {@link mindplot.model.INodeModel}
*/ */
setShapeType(type) { setShapeType(type) {
this._setShapeType(type, true); this._setShapeType(type, true);
} }
@ -331,10 +320,10 @@ class Topic extends NodeGraph {
} }
/** /**
* assigns the new feature model to the topic's node model and adds the respective icon * assigns the new feature model to the topic's node model and adds the respective icon
* @param {mindplot.model.FeatureModel} featureModel * @param {mindplot.model.FeatureModel} featureModel
* @return {mindplot.Icon} the icon corresponding to the feature model * @return {mindplot.Icon} the icon corresponding to the feature model
*/ */
addFeature(featureModel) { addFeature(featureModel) {
const iconGroup = this.getOrBuildIconGroup(); const iconGroup = this.getOrBuildIconGroup();
this.closeEditors(); this.closeEditors();
@ -648,6 +637,9 @@ class Topic extends NodeGraph {
// Register listeners ... // Register listeners ...
this._registerDefaultListenersToElement(group, this); this._registerDefaultListenersToElement(group, this);
// Set test id
group.setTestId(model.getId());
} }
_registerDefaultListenersToElement(elem, topic) { _registerDefaultListenersToElement(elem, topic) {
@ -852,8 +844,8 @@ class Topic extends NodeGraph {
} }
/** /**
* Point: references the center of the rect shape.!!! * Point: references the center of the rect shape.!!!
*/ */
setPosition(point) { setPosition(point) {
$assert(point, 'position can not be null'); $assert(point, 'position can not be null');
// allowed param reassign to avoid risks of existing code relying in this side-effect // allowed param reassign to avoid risks of existing code relying in this side-effect
@ -890,8 +882,9 @@ class Topic extends NodeGraph {
getIncomingLines() { getIncomingLines() {
const children = this.getChildren(); const children = this.getChildren();
return children.filter((node) => $defined(node.getOutgoingLine())) return children
.map(((node) => node.getOutgoingLine())); .filter((node) => $defined(node.getOutgoingLine()))
.map((node) => node.getOutgoingLine());
} }
/** */ /** */
@ -986,8 +979,8 @@ class Topic extends NodeGraph {
const sourceParent = sourceTopic.getModel().getParent(); const sourceParent = sourceTopic.getModel().getParent();
relationship.setVisibility( relationship.setVisibility(
value value
&& (targetParent == null || !targetParent.areChildrenShrunken()) && (targetParent == null || !targetParent.areChildrenShrunken())
&& (sourceParent == null || !sourceParent.areChildrenShrunken()), && (sourceParent == null || !sourceParent.areChildrenShrunken()),
); );
}); });
} }
@ -1055,8 +1048,7 @@ class Topic extends NodeGraph {
}; };
const oldSize = this.getSize(); const oldSize = this.getSize();
const hasSizeChanged = oldSize.width !== roundedSize.width const hasSizeChanged = oldSize.width !== roundedSize.width || oldSize.height !== roundedSize.height;
|| oldSize.height !== roundedSize.height;
if (hasSizeChanged || force) { if (hasSizeChanged || force) {
NodeGraph.prototype.setSize.call(this, roundedSize); NodeGraph.prototype.setSize.call(this, roundedSize);
@ -1329,9 +1321,9 @@ class Topic extends NodeGraph {
} }
/** /**
* @param childTopic * @param childTopic
* @return {Boolean} true if childtopic is a child topic of this topic or the topic itself * @return {Boolean} true if childtopic is a child topic of this topic or the topic itself
*/ */
isChildTopic(childTopic) { isChildTopic(childTopic) {
let result = this.getId() === childTopic.getId(); let result = this.getId() === childTopic.getId();
if (!result) { if (!result) {

View File

@ -34,7 +34,9 @@ class BootstrapDialog extends Options {
this.setOptions(options); this.setOptions(options);
this.options.onEventData.dialog = this; this.options.onEventData.dialog = this;
this._native = $('<div class="modal fade" tabindex="-1"></div>').append('<div class="modal-dialog"></div>'); this._native = $('<div class="modal fade" tabindex="-1"></div>').append(
'<div class="modal-dialog"></div>',
);
const content = $('<div class="modal-content"></div>'); const content = $('<div class="modal-content"></div>');
const header = this._buildHeader(title); const header = this._buildHeader(title);
@ -65,17 +67,31 @@ class BootstrapDialog extends Options {
footer = $('<div class="modal-footer" style="paddingTop:5;textAlign:center">'); footer = $('<div class="modal-footer" style="paddingTop:5;textAlign:center">');
} }
if (this.options.acceptButton) { if (this.options.acceptButton) {
this.acceptButton = $(`<button type="button" class="btn btn-primary" id="acceptBtn" data-dismiss="modal">${$msg('ACCEPT')}</button>`); this.acceptButton = $(
`<button type="button" class="btn btn-primary" id="acceptBtn" data-dismiss="modal">${$msg(
'ACCEPT',
)}</button>`,
);
footer.append(this.acceptButton); footer.append(this.acceptButton);
this.acceptButton.unbind('click').on('click', this.options.onEventData, this.onAcceptClick); this.acceptButton
.unbind('click')
.on('click', this.options.onEventData, this.onAcceptClick);
} }
if (this.options.removeButton) { if (this.options.removeButton) {
this.removeButton = $(`<button type="button" class="btn btn-secondary" id="removeBtn" data-dismiss="modal">${$msg('REMOVE')}</button>`); this.removeButton = $(
`<button type="button" class="btn btn-secondary" id="removeBtn" data-dismiss="modal">${$msg(
'REMOVE',
)}</button>`,
);
footer.append(this.removeButton); footer.append(this.removeButton);
this.removeButton.on('click', this.options.onEventData, this.onRemoveClick); this.removeButton.on('click', this.options.onEventData, this.onRemoveClick);
} }
if (this.options.cancelButton) { if (this.options.cancelButton) {
footer.append(`<button type="button" class="btn btn-secondary" data-dismiss="modal">${$msg('CANCEL')}</button>`); footer.append(
`<button type="button" class="btn btn-secondary" data-dismiss="modal">${$msg(
'CANCEL',
)}</button>`,
);
} }
return footer; return footer;
} }

View File

@ -49,7 +49,10 @@ class BootstrapDialogRequest extends BootstrapDialog {
this._native.find('.modal-body').load(url, () => { this._native.find('.modal-body').load(url, () => {
me.acceptButton.unbind('click').click(() => { me.acceptButton.unbind('click').click(() => {
if ($defined(global.submitDialogForm) && typeof (global.submitDialogForm) === 'function') { if (
$defined(global.submitDialogForm)
&& typeof global.submitDialogForm === 'function'
) {
global.submitDialogForm(); global.submitDialogForm();
} }
}); });
@ -61,7 +64,7 @@ class BootstrapDialogRequest extends BootstrapDialog {
} }
onDialogShown() { onDialogShown() {
if ($defined(global.onDialogShown) && typeof (global.onDialogShown) === 'function') { if ($defined(global.onDialogShown) && typeof global.onDialogShown === 'function') {
global.onDialogShown(); global.onDialogShown();
} }
} }

View File

@ -61,7 +61,7 @@ class XMLSerializerPela implements XMLMindmapSerializer {
relationships.forEach((relationship) => { relationships.forEach((relationship) => {
if ( if (
mindmap.findNodeById(relationship.getFromNode()) !== null mindmap.findNodeById(relationship.getFromNode()) !== null
&& mindmap.findNodeById(relationship.getToNode()) !== null && mindmap.findNodeById(relationship.getToNode()) !== null
) { ) {
// Isolated relationships are not persisted .... // Isolated relationships are not persisted ....
const relationDom = XMLSerializerPela._relationshipToXML(document, relationship); const relationDom = XMLSerializerPela._relationshipToXML(document, relationship);
@ -83,7 +83,9 @@ class XMLSerializerPela implements XMLMindmapSerializer {
parentTopic.setAttribute('position', `${pos.x},${pos.y}`); parentTopic.setAttribute('position', `${pos.x},${pos.y}`);
const order = topic.getOrder(); const order = topic.getOrder();
if (typeof order === 'number' && Number.isFinite(order)) { parentTopic.setAttribute('order', order.toString()); } if (typeof order === 'number' && Number.isFinite(order)) {
parentTopic.setAttribute('order', order.toString());
}
} }
const text = topic.getText(); const text = topic.getText();
@ -99,8 +101,7 @@ class XMLSerializerPela implements XMLMindmapSerializer {
const size = topic.getImageSize(); const size = topic.getImageSize();
parentTopic.setAttribute( parentTopic.setAttribute(
'image', 'image',
`${size.width},${size.height `${size.width},${size.height}:${topic.getImageUrl()}`,
}:${topic.getImageUrl()}`,
); );
} }
} }
@ -132,10 +133,10 @@ class XMLSerializerPela implements XMLMindmapSerializer {
if ( if (
$defined(fontFamily) $defined(fontFamily)
|| $defined(fontSize) || $defined(fontSize)
|| $defined(fontColor) || $defined(fontColor)
|| $defined(fontWeight) || $defined(fontWeight)
|| $defined(fontStyle) || $defined(fontStyle)
) { ) {
parentTopic.setAttribute('fontStyle', font); parentTopic.setAttribute('fontStyle', font);
} }
@ -226,13 +227,13 @@ class XMLSerializerPela implements XMLMindmapSerializer {
} }
/** /**
* @param dom * @param dom
* @param mapId * @param mapId
* @throws will throw an error if dom is null or undefined * @throws will throw an error if dom is null or undefined
* @throws will throw an error if mapId is null or undefined * @throws will throw an error if mapId is null or undefined
* @throws will throw an error if the document element is not consistent with a wisemap's root * @throws will throw an error if the document element is not consistent with a wisemap's root
* element * element
*/ */
loadFromDom(dom: Document, mapId: string) { loadFromDom(dom: Document, mapId: string) {
$assert(dom, 'dom can not be null'); $assert(dom, 'dom can not be null');
$assert(mapId, 'mapId can not be null'); $assert(mapId, 'mapId can not be null');
@ -253,7 +254,9 @@ class XMLSerializerPela implements XMLMindmapSerializer {
// Add all the topics nodes ... // Add all the topics nodes ...
const childNodes = Array.from(rootElem.childNodes); const childNodes = Array.from(rootElem.childNodes);
const topicsNodes = childNodes const topicsNodes = childNodes
.filter((child: ChildNode) => (child.nodeType === 1 && (child as Element).tagName === 'topic')) .filter(
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'topic',
)
.map((c) => c as Element); .map((c) => c as Element);
topicsNodes.forEach((child) => { topicsNodes.forEach((child) => {
const topic = this._deserializeNode(child, mindmap); const topic = this._deserializeNode(child, mindmap);
@ -262,7 +265,9 @@ class XMLSerializerPela implements XMLMindmapSerializer {
// Then all relationshops, they are connected to topics ... // Then all relationshops, they are connected to topics ...
const relationshipsNodes = childNodes const relationshipsNodes = childNodes
.filter((child: ChildNode) => (child.nodeType === 1 && (child as Element).tagName === 'relationship')) .filter(
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'relationship',
)
.map((c) => c as Element); .map((c) => c as Element);
relationshipsNodes.forEach((child) => { relationshipsNodes.forEach((child) => {
try { try {
@ -280,9 +285,7 @@ class XMLSerializerPela implements XMLMindmapSerializer {
} }
_deserializeNode(domElem: Element, mindmap: Mindmap) { _deserializeNode(domElem: Element, mindmap: Mindmap) {
const type = domElem.getAttribute('central') != null const type = domElem.getAttribute('central') != null ? 'CentralTopic' : 'MainTopic';
? 'CentralTopic'
: 'MainTopic';
// Load attributes... // Load attributes...
let id: number | null = null; let id: number | null = null;
@ -480,16 +483,16 @@ class XMLSerializerPela implements XMLMindmapSerializer {
} }
/** /**
* This method ensures that the output String has only * This method ensures that the output String has only
* valid XML unicode characters as specified by the * valid XML unicode characters as specified by the
* XML 1.0 standard. For reference, please see * XML 1.0 standard. For reference, please see
* <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the * <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
* standard</a>. This method will return an empty * standard</a>. This method will return an empty
* String if the input is null or empty. * String if the input is null or empty.
* *
* @param in The String whose non-valid characters we want to remove. * @param in The String whose non-valid characters we want to remove.
* @return The in String, stripped of non-valid characters. * @return The in String, stripped of non-valid characters.
*/ */
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
rmXmlInv(str) { rmXmlInv(str) {
if (str == null || str === undefined) return null; if (str == null || str === undefined) return null;
@ -499,11 +502,11 @@ class XMLSerializerPela implements XMLMindmapSerializer {
const c = str.charCodeAt(i); const c = str.charCodeAt(i);
if ( if (
c === 0x9 c === 0x9
|| c === 0xa || c === 0xa
|| c === 0xd || c === 0xd
|| (c >= 0x20 && c <= 0xd7ff) || (c >= 0x20 && c <= 0xd7ff)
|| (c >= 0xe000 && c <= 0xfffd) || (c >= 0xe000 && c <= 0xfffd)
|| (c >= 0x10000 && c <= 0x10ffff) || (c >= 0x10000 && c <= 0x10ffff)
) { ) {
result += str.charAt(i); result += str.charAt(i);
} }

View File

@ -21,11 +21,11 @@ import ToolbarPaneItem from './ToolbarPaneItem';
import { buildHtml, css } from './ColorPaletteHtml'; import { buildHtml, css } from './ColorPaletteHtml';
// rgbToHex implementation from https://stackoverflow.com/a/3627747/58128 // rgbToHex implementation from https://stackoverflow.com/a/3627747/58128
export const rgb2hex = (rgb) => `#${ export const rgb2hex = (rgb) => `#${rgb
rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/) .match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/)
.slice(1) .slice(1)
.map((n) => parseInt(n, 10) .map((n) => parseInt(n, 10).toString(16).padStart(2, '0'))
.toString(16).padStart(2, '0')).join('')}`; .join('')}`;
class ColorPalettePanel extends ToolbarPaneItem { class ColorPalettePanel extends ToolbarPaneItem {
constructor(buttonId, model, baseUrl) { constructor(buttonId, model, baseUrl) {
@ -38,10 +38,7 @@ class ColorPalettePanel extends ToolbarPaneItem {
_load() { _load() {
if (!ColorPalettePanel._panelContent) { if (!ColorPalettePanel._panelContent) {
// Load all the CSS styles ... // Load all the CSS styles ...
$('<style>') $('<style>').append(css).appendTo($('head')).attr({ type: 'text/css' });
.append(css)
.appendTo($('head'))
.attr({ type: 'text/css' });
ColorPalettePanel._panelContent = buildHtml(); ColorPalettePanel._panelContent = buildHtml();
} }

View File

@ -20,9 +20,7 @@ module.exports = {
{ {
test: /.(js$)/, test: /.(js$)/,
use: ['babel-loader'], use: ['babel-loader'],
exclude: [ exclude: [/node_modules/],
/node_modules/,
],
enforce: 'pre', enforce: 'pre',
}, },
{ {

View File

@ -8,23 +8,25 @@ const common = require('./webpack.common');
const playgroundConfig = { const playgroundConfig = {
mode: 'development', mode: 'development',
entry: { entry: {
mindplot: path.resolve(__dirname, './src/index.js'),
layout: path.resolve(__dirname, './test/playground/layout/context-loader'), layout: path.resolve(__dirname, './test/playground/layout/context-loader'),
}, },
output: {
path: path.resolve(__dirname, 'test/playground/dist'),
filename: '[name].js',
library: {
type: 'umd',
},
},
devServer: { devServer: {
historyApiFallback: true, historyApiFallback: true,
port: 8083, port: 8083,
open: false, open: false,
writeToDisk: true,
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.css$/i, test: /\.css$/i,
use: [ use: ['style-loader', 'css-loader?url=false'],
'style-loader',
'css-loader?url=false',
],
}, },
], ],
}, },

View File

@ -117,9 +117,9 @@ class ElementClass {
} }
/** /**
* /* * /*
* Returns element type name. * Returns element type name.
*/ */
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
getType() { getType() {
throw new Error( throw new Error(
@ -161,12 +161,12 @@ class ElementClass {
setStroke(width, style, color, opacity) { setStroke(width, style, color, opacity) {
if ( if (
style != null style != null
&& style !== undefined && style !== undefined
&& style !== 'dash' && style !== 'dash'
&& style !== 'dot' && style !== 'dot'
&& style !== 'solid' && style !== 'solid'
&& style !== 'longdash' && style !== 'longdash'
&& style !== 'dashdot' && style !== 'dashdot'
) { ) {
throw new Error(`Unsupported stroke style: '${style}'`); throw new Error(`Unsupported stroke style: '${style}'`);
} }
@ -288,6 +288,10 @@ class ElementClass {
getParent() { getParent() {
return this.peer.getParent(); return this.peer.getParent();
} }
setTestId(testId) {
this.peer._native.setAttribute('test-id', testId);
}
} }
Element._SIGNATURE_MULTIPLE_ARGUMENTS = -1; Element._SIGNATURE_MULTIPLE_ARGUMENTS = -1;

View File

@ -51,7 +51,6 @@ class WorkspacePeer extends ElementPeer {
setCoordSize(width, height) { setCoordSize(width, height) {
const viewBox = this._native.getAttribute('viewBox'); const viewBox = this._native.getAttribute('viewBox');
let coords = [0, 0, 0, 0]; let coords = [0, 0, 0, 0];
if (viewBox != null) { if (viewBox != null) {
coords = viewBox.split(/ /); coords = viewBox.split(/ /);

View File

@ -20,7 +20,7 @@ module.exports = {
image: './test/playground/image.js', image: './test/playground/image.js',
}, },
output: { output: {
path: path.resolve(__dirname, 'dist', 'tests'), path: path.resolve(__dirname, 'test/playground/dist'),
filename: '[name].js', filename: '[name].js',
publicPath: '/', publicPath: '/',
}, },
@ -36,9 +36,7 @@ module.exports = {
{ {
use: 'babel-loader', use: 'babel-loader',
test: /.js$/, test: /.js$/,
exclude: [ exclude: [/node_modules/],
/node_modules/,
],
}, },
{ {
test: /\.(png|svg)$/i, test: /\.(png|svg)$/i,
@ -55,107 +53,77 @@ module.exports = {
plugins: [ plugins: [
new CleanWebpackPlugin(), new CleanWebpackPlugin(),
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [{ from: 'test/playground/styles.css', to: 'styles.css' }],
{ from: 'test/playground/styles.css', to: 'styles.css' }, }),
], new HtmlWebpackPlugin({
chunks: ['index'],
filename: 'index.html',
template: 'test/playground/index.html',
}),
new HtmlWebpackPlugin({
chunks: ['arrow'],
filename: 'arrow.html',
template: 'test/playground/arrow.html',
}),
new HtmlWebpackPlugin({
chunks: ['curvedLine'],
filename: 'curvedLine.html',
template: 'test/playground/curvedLine.html',
}),
new HtmlWebpackPlugin({
chunks: ['events'],
filename: 'events.html',
template: 'test/playground/events.html',
}),
new HtmlWebpackPlugin({
chunks: ['font'],
filename: 'font.html',
template: 'test/playground/font.html',
}),
new HtmlWebpackPlugin({
chunks: ['rect'],
filename: 'rect.html',
template: 'test/playground/rect.html',
}),
new HtmlWebpackPlugin({
chunks: ['line'],
filename: 'line.html',
template: 'test/playground/line.html',
}),
new HtmlWebpackPlugin({
chunks: ['workspace'],
filename: 'workspace.html',
template: 'test/playground/workspace.html',
}),
new HtmlWebpackPlugin({
chunks: ['polyLine'],
filename: 'polyLine.html',
template: 'test/playground/polyLine.html',
}),
new HtmlWebpackPlugin({
chunks: ['shapes'],
filename: 'shapes.html',
template: 'test/playground/shapes.html',
}),
new HtmlWebpackPlugin({
chunks: ['group'],
filename: 'group.html',
template: 'test/playground/group.html',
}),
new HtmlWebpackPlugin({
chunks: ['prototype'],
filename: 'prototype.html',
template: 'test/playground/prototype.html',
}),
new HtmlWebpackPlugin({
chunks: ['text'],
filename: 'text.html',
template: 'test/playground/text.html',
}),
new HtmlWebpackPlugin({
chunks: ['image'],
filename: 'image.html',
template: 'test/playground/image.html',
}), }),
new HtmlWebpackPlugin(
{
chunks: ['index'],
filename: 'index.html',
template: 'test/playground/index.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['arrow'],
filename: 'arrow.html',
template: 'test/playground/arrow.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['curvedLine'],
filename: 'curvedLine.html',
template: 'test/playground/curvedLine.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['events'],
filename: 'events.html',
template: 'test/playground/events.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['font'],
filename: 'font.html',
template: 'test/playground/font.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['rect'],
filename: 'rect.html',
template: 'test/playground/rect.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['line'],
filename: 'line.html',
template: 'test/playground/line.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['workspace'],
filename: 'workspace.html',
template: 'test/playground/workspace.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['polyLine'],
filename: 'polyLine.html',
template: 'test/playground/polyLine.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['shapes'],
filename: 'shapes.html',
template: 'test/playground/shapes.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['group'],
filename: 'group.html',
template: 'test/playground/group.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['prototype'],
filename: 'prototype.html',
template: 'test/playground/prototype.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['text'],
filename: 'text.html',
template: 'test/playground/text.html',
},
),
new HtmlWebpackPlugin(
{
chunks: ['image'],
filename: 'image.html',
template: 'test/playground/image.html',
},
),
], ],
}; };

View File

@ -1,35 +1,36 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
const path = require('path'); const path = require('path');
const webpack = require('webpack') const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
webpack webpack;
module.exports = { module.exports = {
entry: { entry: {
app: path.join(__dirname, 'src', 'index.tsx') app: path.join(__dirname, 'src', 'index.tsx'),
}, },
target: 'web', target: 'web',
resolve: { resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'] extensions: ['.ts', '.tsx', '.js', '.jsx'],
}, },
output: { output: {
filename: '[name].bundle.js', filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist') path: path.resolve(__dirname, 'dist'),
}, },
module: { module: {
rules: [{ rules: [
test: /\.tsx?$/, {
use: 'ts-loader', test: /\.tsx?$/,
exclude: '/node_modules/' use: 'ts-loader',
}, exclude: '/node_modules/',
{ },
test: /\.(png|jpe?g|gif|svg)$/, {
type: 'asset/inline', test: /\.(png|jpe?g|gif|svg)$/,
}, type: 'asset/inline',
] },
],
}, },
optimization: { optimization: {
usedExports: true, usedExports: true,
@ -37,23 +38,24 @@ module.exports = {
cacheGroups: { cacheGroups: {
vendors: { vendors: {
test: /node_modules\/.*/, test: /node_modules\/.*/,
name: "vendors", name: 'vendors',
chunks: "all", chunks: 'all',
} },
}, },
} },
}, },
plugins: [ plugins: [
new CleanWebpackPlugin(), new CleanWebpackPlugin(),
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: [{ patterns: [
from: 'public/*', {
to: '[name].[ext]', from: 'public/*',
globOptions: { to: '[name].[ext]',
ignore: [ globOptions: {
'**/index.html' ignore: ['**/index.html'],
] },
} },
}] ],
})] }),
} ],
};

View File

@ -4,18 +4,20 @@ const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(common, { module.exports = merge(common, {
mode: 'production', mode: 'production',
devtool: 'source-map', devtool: 'source-map',
optimization: { optimization: {
minimize: true minimize: true,
}, },
plugins: [ plugins: [
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: path.join(__dirname, 'public/index.html'), template: path.join(__dirname, 'public/index.html'),
templateParameters: { templateParameters: {
PUBLIC_URL: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'https://www.wisemapping.com' PUBLIC_URL: process.env.PUBLIC_URL
}, ? process.env.PUBLIC_URL
base: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'https://www.wisemapping.com' : 'https://www.wisemapping.com',
}) },
] base: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'https://www.wisemapping.com',
}),
],
}); });

2582
yarn.lock

File diff suppressed because it is too large Load Diff