Skip to content
Open
Changes from 1 commit
Commits
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
265 changes: 265 additions & 0 deletions test/qml/tst_geometryeditor_vertex.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import QtQuick
import QtTest
import org.qfield
import org.qgis
import Theme
import "../../src/qml/geometryeditors" as GeometryEditors

// tests for the vertex editor tool. The vertex model itself is tested in
// test_vertexmodel.cpp, so here we only check the tool around it: init wiring,
// applyChanges and the autoSave branch, cancel, remove, and undo.
//
// canvasClicked is not tested here on purpose. It turns a screen point into a
// map coordinate through mapSettings.screenToCoordinate, which only makes sense
// with a real rendered canvas. without one the screen-to-map math is meaningless,
// so a click test would just be asserting made up numbers, so that path belongs in
// an integration (spix) test, not here.
//
// the tool reads a few things from its parent scope in the app, so we provide
// them here as plain items the same way tst_featureForm.qml does.
TestCase {
id: testCase
name: "GeometryEditorVertex"

property var fieldsLayer: qgisProject.mapLayersByName("Fields")[0]
property var tracksLayer: qgisProject.mapLayersByName("Tracks")[0]

// we never commit to the layer, so just make sure any open edit session is
// rolled back between tests and the layer is left clean
function cleanup() {
vertexEditor.cancel();
if (fieldsLayer.editBuffer()) {
fieldsLayer.rollBack();
}
if (tracksLayer.editBuffer()) {
tracksLayer.rollBack();
}
qfieldSettings.autoSave = false;
}

function makeFeatureModel(layer, fid) {
featureModel.currentLayer = layer;
featureModel.feature = layer.getFeature(fid);
featureModel.applyGeometryToVertexModel();
return featureModel;
}

// Fields/39 is a simple polygon. its vertex model interleaves segment
// candidates with real vertices, so the real (existing) vertices sit at the
// odd indices and 1 is the first of them.
function makeFieldsModel() {
return makeFeatureModel(fieldsLayer, "39");
}

// select the first real vertex and move it, so the model goes dirty like it
// would after a drag on the canvas. the layer is in meters so a meter-scale
// move is well above the tools ignore-tiny-moves threshold.
function selectAndMoveFirstVertex() {
const vertexModel = featureModel.vertexModel;
vertexModel.editingMode = VertexModel.EditVertex;
vertexModel.currentVertexIndex = 1;
const point = vertexModel.currentPoint;
vertexModel.currentPoint = GeometryUtils.point(point.x + 5, point.y + 5);
}

MapSettings {
id: mapSettingsItem
destinationCrs: CoordinateReferenceSystemUtils.fromDescription("EPSG:3857")
}

FeatureModel {
id: featureModel
project: qgisProject

vertexModel: VertexModel {
id: geometryEditingVertexModel
}
}

GeometryEditors.VertexEditor {
id: vertexEditor
featureModel: featureModel
mapSettings: mapSettingsItem
}

function test_initWiresFeatureModelAndResetsCurrentVertex() {
const model = makeFieldsModel();
model.vertexModel.currentVertexIndex = 3;

vertexEditor.init(model, mapSettingsItem, null, null);

compare(vertexEditor.featureModel, model);
// init should always start with no vertex selected
compare(model.vertexModel.currentVertexIndex, -1);
}

function test_blockingFollowsVertexModelDirtyState() {
const model = makeFieldsModel();
vertexEditor.init(model, mapSettingsItem, null, null);
compare(vertexEditor.blocking, false);

selectAndMoveFirstVertex();

// blocking is just dirty, so an unsaved edit should block
compare(model.vertexModel.dirty, true);
compare(vertexEditor.blocking, true);
}

function test_applyChangesAppliesMovedVertexToLayerBuffer() {
const model = makeFieldsModel();
vertexEditor.init(model, mapSettingsItem, null, null);

selectAndMoveFirstVertex();
const before = model.feature.geometry.asWkt();

// apply pushes the vertex model edit onto the feature. we check the feature
// geometry changed but never commit, so the layer file is untouched
vertexEditor.applyChanges(true);

verify(model.feature.geometry.asWkt() !== before);
Comment thread
kaustuvpokharel marked this conversation as resolved.
Outdated
}

function test_applyChangesWithAutoSaveOffDoesNotApply() {
qfieldSettings.autoSave = false;
const model = makeFieldsModel();
vertexEditor.init(model, mapSettingsItem, null, null);

selectAndMoveFirstVertex();
const before = model.feature.geometry.asWkt();

// the next/prev/add buttons call applyChanges(autoSave). with autoSave off
// the edit stays in the model and is not pushed to the feature yet
vertexEditor.applyChanges(qfieldSettings.autoSave);

compare(model.feature.geometry.asWkt(), before);
compare(model.vertexModel.dirty, true);
}

function test_applyChangesNoOpWhenNotDirty() {
const model = makeFieldsModel();
vertexEditor.init(model, mapSettingsItem, null, null);
compare(model.vertexModel.dirty, false);

const before = model.feature.geometry.asWkt();

// nothing changed, so apply should do nothing
vertexEditor.applyChanges(true);

compare(model.feature.geometry.asWkt(), before);
}

function test_cancelResetsEditingModeAndClearsDirty() {
const model = makeFieldsModel();
vertexEditor.init(model, mapSettingsItem, null, null);
selectAndMoveFirstVertex();
compare(model.vertexModel.dirty, true);

vertexEditor.cancel();

// cancel throws away the edit and leaves editing mode
compare(model.vertexModel.editingMode, VertexModel.NoEditing);
compare(model.vertexModel.dirty, false);
}

function test_removeVertexReducesCountAndSetsDirty() {
const model = makeFieldsModel();
vertexEditor.init(model, mapSettingsItem, null, null);
const vertexModel = model.vertexModel;

vertexModel.editingMode = VertexModel.EditVertex;
vertexModel.currentVertexIndex = 1;
// the polygon has 12 real vertices, well above the minimum, so removing one is allowed
verify(vertexModel.canRemoveVertex);
const countBefore = vertexModel.vertexCount;

vertexModel.removeCurrentVertex();

// removing a vertex drops the row count and marks the edit dirty
verify(vertexModel.vertexCount < countBefore);
Comment thread
kaustuvpokharel marked this conversation as resolved.
Outdated
compare(vertexModel.dirty, true);
}

function test_canRemoveVertexFalseWithoutEditMode() {
const model = makeFieldsModel();
vertexEditor.init(model, mapSettingsItem, null, null);

// canRemoveVertex only holds while a vertex is being edited, never before
compare(model.vertexModel.editingMode, VertexModel.NoEditing);
compare(model.vertexModel.canRemoveVertex, false);
}

function test_undoRestoresGeometryAfterMove() {
const model = makeFieldsModel();
vertexEditor.init(model, mapSettingsItem, null, null);
const vertexModel = model.vertexModel;

const before = vertexModel.currentPoint;
selectAndMoveFirstVertex();
verify(vertexModel.canUndo);

vertexModel.undoHistory();

// undo should put the moved vertex back where it started
vertexModel.currentVertexIndex = 1;
const restored = vertexModel.currentPoint;
compare(restored.x, before.x);
compare(restored.y, before.y);
}

function test_undoIsNoOpWithEmptyHistory() {
const model = makeFieldsModel();
vertexEditor.init(model, mapSettingsItem, null, null);

// nothing has been edited, so there is nothing to undo
compare(model.vertexModel.canUndo, false);
model.vertexModel.undoHistory();
compare(model.vertexModel.dirty, false);
}

function test_applyChangesAppliesMovedVertexOnLineLayer() {
// same apply path but on a line feature, to cover the line geometry branch
const model = makeFeatureModel(tracksLayer, "1");
vertexEditor.init(model, mapSettingsItem, null, null);
const vertexModel = model.vertexModel;

// on a line the first real vertex is also at an odd index
vertexModel.editingMode = VertexModel.EditVertex;
vertexModel.currentVertexIndex = 1;
const point = vertexModel.currentPoint;
vertexModel.currentPoint = GeometryUtils.point(point.x + 5, point.y + 5);
compare(vertexModel.dirty, true);

const before = model.feature.geometry.asWkt();
vertexEditor.applyChanges(true);

verify(model.feature.geometry.asWkt() !== before);
Comment thread
kaustuvpokharel marked this conversation as resolved.
Outdated
}
Comment thread
kaustuvpokharel marked this conversation as resolved.

// scope objects the tool expects from the app
Item {
id: mainWindowItem
}

Item {
id: qfieldSettings
property bool autoSave: false
}

Item {
id: coordinateLocator
property var currentCoordinate: GeometryUtils.point(0, 0)
property string positionInformation: ""
property string topSnappingResult: ""
property bool positionLocked: false
}

Item {
id: projectInfo
property string cloudUserInformation: ""
}

Item {
id: positionSource
property string positionInformation: ""
}
}
Loading