Add a "user overlay" tool: click the map, save a note
New map-corner "Add note" control (public/js/notes.js), mirroring
measure.js's click-to-place UX. Click the map, write a short note,
pick an existing .geojson layer file or name a new one — saved via a
new GpxStore#append_feature (grows the target file in place, unlike
save()'s always-disambiguate-the-filename behavior) through a new
POST /api/gpx/notes route.
No new rendering path: a note is just another point in a .geojson
file the Tracks panel's tree already lists, checkboxes, and pops up —
visibility toggling ("layers") comes for free from that existing
machinery, per-file, with no changes needed there.
Points only for now — a polygon mode would need gpx.js's point/line
split taught a third Polygon bucket first, or a saved polygon
wouldn't render back on reload (see TODO.adoc).
Covered by new tests: GpxStore#append_feature (create/append/missing-
path/filename-safety) and the /api/gpx/notes route (auth gate, create,
append, malformed/missing-geometry rejection, 404 on a missing
target). Full suite: 97 runs, 0 failures.
This commit is contained in:
1 parent
5caf865fc8
commit
aa2b96ac70
11 files changed
+437
-2
No files matched your search
@@ -176,6 +176,47 @@ class AppTest < Minitest::Test
|
||||
assert_equal 400, response.status
|
||||
end
|
||||
|
||||
# --- Notes (hand-drawn points, notes.js) ---
|
||||
|
||||
def test_notes_requires_login
|
||||
assert_equal 401, request.post("/api/gpx/notes", params: { feature: sample_note_feature, name: "Notes" }).status
|
||||
end
|
||||
|
||||
def test_notes_creates_a_new_layer_file
|
||||
response = request.post("/api/gpx/notes", params: { feature: sample_note_feature, name: "My Notes" }, **logged_in_env)
|
||||
|
||||
assert_equal 200, response.status
|
||||
body = json_body(response)
|
||||
assert_equal "My Notes.geojson", body["path"]
|
||||
assert_equal ["My Notes.geojson"], body["tree"]["children"].map { |c| c["name"] }
|
||||
end
|
||||
|
||||
def test_notes_appends_to_an_existing_layer_file_instead_of_disambiguating
|
||||
first = json_body(request.post("/api/gpx/notes", params: { feature: sample_note_feature("Turning spot"), name: "My Notes" }, **logged_in_env))
|
||||
|
||||
second = request.post("/api/gpx/notes", params: { feature: sample_note_feature("Narrow gate"), path: first["path"] }, **logged_in_env)
|
||||
assert_equal 200, second.status
|
||||
assert_equal first["path"], json_body(second)["path"]
|
||||
|
||||
features = json_body(request.get("/api/gpx/file/#{Rack::Utils.escape_path(first["path"])}"))["features"]
|
||||
assert_equal ["Turning spot", "Narrow gate"], features.map { |f| f["properties"]["name"] }
|
||||
end
|
||||
|
||||
def test_notes_rejects_malformed_feature_json
|
||||
response = request.post("/api/gpx/notes", params: { feature: "not json", name: "Notes" }, **logged_in_env)
|
||||
assert_equal 400, response.status
|
||||
end
|
||||
|
||||
def test_notes_rejects_a_feature_with_no_geometry
|
||||
response = request.post("/api/gpx/notes", params: { feature: '{"type":"Feature","properties":{}}', name: "Notes" }, **logged_in_env)
|
||||
assert_equal 400, response.status
|
||||
end
|
||||
|
||||
def test_notes_to_a_missing_path_is_404
|
||||
response = request.post("/api/gpx/notes", params: { feature: sample_note_feature, path: "nope.geojson" }, **logged_in_env)
|
||||
assert_equal 404, response.status
|
||||
end
|
||||
|
||||
# --- LiveTrack webhook ---
|
||||
|
||||
def test_livetrack_webhook_requires_the_configured_secret
|
||||
@@ -214,6 +255,14 @@ class AppTest < Minitest::Test
|
||||
|
||||
private
|
||||
|
||||
def sample_note_feature(name = "Turning spot")
|
||||
JSON.generate(
|
||||
"type" => "Feature",
|
||||
"properties" => { "name" => name, "category" => "Note" },
|
||||
"geometry" => { "type" => "Point", "coordinates" => [17.1, 48.1] },
|
||||
)
|
||||
end
|
||||
|
||||
def le32(n)
|
||||
[n].pack("l<")
|
||||
end
|
||||
|
||||
Reference in new issue
Block a user