diff --git a/lib/services/node_cache.dart b/lib/services/node_cache.dart index 39a0207..9a29be3 100644 --- a/lib/services/node_cache.dart +++ b/lib/services/node_cache.dart @@ -30,7 +30,9 @@ class NodeCache { id: node.id, coord: node.coord, tags: mergedTags, - isConstrained: node.isConstrained, // Preserve constraint information + // Once a node is known to be constrained, keep it that way - see + // NodeSpatialCache.addOrUpdateNodes for the full explanation. + isConstrained: existing.isConstrained || node.isConstrained, ); } else { _nodes[node.id] = node; diff --git a/lib/services/node_spatial_cache.dart b/lib/services/node_spatial_cache.dart index 35d9044..1360e90 100644 --- a/lib/services/node_spatial_cache.dart +++ b/lib/services/node_spatial_cache.dart @@ -58,7 +58,13 @@ class NodeSpatialCache { id: node.id, coord: node.coord, tags: mergedTags, - isConstrained: node.isConstrained, + // Once a node is known to be constrained (part of a way/relation), + // keep treating it as constrained. Callers that build a fresh + // OsmNode for cache updates (e.g. after an edit upload succeeds) + // don't have way/relation data and default to false, so blindly + // taking the incoming value would "unconstrain" the node until + // the next Overpass fetch. + isConstrained: existing.isConstrained || node.isConstrained, ); } else { _nodes[node.id] = node; diff --git a/test/services/node_spatial_cache_test.dart b/test/services/node_spatial_cache_test.dart new file mode 100644 index 0000000..1b7122c --- /dev/null +++ b/test/services/node_spatial_cache_test.dart @@ -0,0 +1,65 @@ +import 'package:deflockapp/models/osm_node.dart'; +import 'package:deflockapp/services/node_spatial_cache.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:latlong2/latlong.dart'; + +void main() { + group('NodeSpatialCache constrained flag', () { + setUp(() { + // NodeSpatialCache is a singleton - start each test from a clean slate. + NodeSpatialCache().clear(); + }); + + test('stays constrained after an update that omits the flag', () { + final cache = NodeSpatialCache(); + const nodeId = 123; + + cache.addOrUpdateNodes([ + OsmNode( + id: nodeId, + coord: const LatLng(0, 0), + tags: const {}, + isConstrained: true, + ), + ]); + + // Simulate what happens after an edit upload succeeds: a brand-new + // OsmNode is built from upload data, which has no way/relation + // information and so defaults isConstrained to false. + cache.addOrUpdateNodes([ + OsmNode( + id: nodeId, + coord: const LatLng(1, 1), + tags: const {'amenity': 'surveillance'}, + ), + ]); + + final updated = cache.getNodeById(nodeId); + expect(updated, isNotNull); + expect(updated!.isConstrained, isTrue); + // Non-constraint fields should still be updated as normal. + expect(updated.coord, const LatLng(1, 1)); + expect(updated.tags['amenity'], 'surveillance'); + }); + + test('becomes constrained if a later update says it is', () { + final cache = NodeSpatialCache(); + const nodeId = 456; + + cache.addOrUpdateNodes([ + OsmNode(id: nodeId, coord: const LatLng(0, 0), tags: const {}), + ]); + + cache.addOrUpdateNodes([ + OsmNode( + id: nodeId, + coord: const LatLng(0, 0), + tags: const {}, + isConstrained: true, + ), + ]); + + expect(cache.getNodeById(nodeId)!.isConstrained, isTrue); + }); + }); +}