Add polygon geographies to Budgets' map
Note that in the budgets wizard test we now create district with no associated geozone, so the text "all city" will appear in the districts table too, meaning we can't use `within "section", text: "All city" do` anymore since it would result in an ambiguous match. Co-Authored-By: Julian Herrero <microweb10@gmail.com> Co-Authored-By: Javi Martín <javim@elretirao.net>
This commit is contained in:
committed by
Javi Martín
parent
d1f1e1dfea
commit
de13e789dd
@@ -27,11 +27,12 @@
|
||||
});
|
||||
},
|
||||
synchronizeInputs: function() {
|
||||
var banners, inputs, processes, progress_bar;
|
||||
var banners, geozones, inputs, processes, progress_bar;
|
||||
progress_bar = "[name='progress_bar[percentage]']";
|
||||
processes = "[name='legislation_process[background_color]'], [name='legislation_process[font_color]']";
|
||||
banners = "[name='banner[background_color]'], [name='banner[font_color]']";
|
||||
inputs = $(progress_bar + ", " + processes + ", " + banners);
|
||||
geozones = "[name='geozone[color]']";
|
||||
inputs = $(progress_bar + ", " + processes + ", " + banners + ", " + geozones);
|
||||
inputs.on({
|
||||
input: function() {
|
||||
$("[name='" + this.name + "']").val($(this).val());
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
}
|
||||
|
||||
App.Map.addInvestmentsMarkers(investmentsMarkers, createMarker);
|
||||
App.Map.addGeozones(map);
|
||||
},
|
||||
leafletMap: function(element) {
|
||||
var centerData, mapCenterLatLng;
|
||||
@@ -194,6 +195,25 @@
|
||||
map.attributionControl.setPrefix(App.Map.attributionPrefix());
|
||||
L.tileLayer(mapTilesProvider, { attribution: mapAttribution }).addTo(map);
|
||||
},
|
||||
addGeozones: function(map) {
|
||||
var geozones = $(map._container).data("geozones");
|
||||
|
||||
if (geozones) {
|
||||
geozones.forEach(function(geozone) {
|
||||
App.Map.addGeozone(geozone, map);
|
||||
});
|
||||
}
|
||||
},
|
||||
addGeozone: function(geozone, map) {
|
||||
var polygon = L.polygon(geozone.outline_points, {
|
||||
color: geozone.color,
|
||||
fillOpacity: 0.3,
|
||||
className: "map-polygon"
|
||||
});
|
||||
|
||||
polygon.bindPopup(geozone.headings.join("<br>"));
|
||||
polygon.addTo(map);
|
||||
},
|
||||
openMarkerPopup: function(e) {
|
||||
var marker = e.target;
|
||||
$.ajax("/investments/" + marker.options.id + "/json_data", {
|
||||
|
||||
@@ -18,6 +18,13 @@
|
||||
<%= f.hidden_field :price, value: 0 %>
|
||||
<% end %>
|
||||
|
||||
<% if feature?(:map) %>
|
||||
<%= f.select :geozone_id,
|
||||
geozone_options,
|
||||
include_blank: t("geozones.none"),
|
||||
hint: t("admin.budget_headings.form.geozone_info") %>
|
||||
<% end %>
|
||||
|
||||
<% if heading.budget.approval_voting? %>
|
||||
<%= f.number_field :max_ballot_lines,
|
||||
hint: t("admin.budget_headings.form.max_ballot_lines_info") %>
|
||||
|
||||
@@ -18,4 +18,8 @@ class Admin::BudgetHeadings::FormComponent < ApplicationComponent
|
||||
def single_heading?
|
||||
helpers.respond_to?(:single_heading?) && helpers.single_heading?
|
||||
end
|
||||
|
||||
def geozone_options
|
||||
Geozone.all.map { |geozone| [geozone.name, geozone.id] }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<% if budget.approval_voting? %>
|
||||
<th><%= Budget::Heading.human_attribute_name(:max_ballot_lines) %></th>
|
||||
<% end %>
|
||||
<th><%= Budget::Heading.human_attribute_name(:geozone_id) %></th>
|
||||
<th><%= t("admin.actions.actions") %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -23,6 +24,9 @@
|
||||
<% if budget.approval_voting? %>
|
||||
<td><%= heading.max_ballot_lines %></td>
|
||||
<% end %>
|
||||
<td>
|
||||
<%= geozone_for(heading) %>
|
||||
</td>
|
||||
<td>
|
||||
<%= render Admin::TableActionsComponent.new(heading) %>
|
||||
</td>
|
||||
|
||||
@@ -14,4 +14,12 @@ class Admin::BudgetHeadings::HeadingsComponent < ApplicationComponent
|
||||
def budget
|
||||
@budget ||= group.budget
|
||||
end
|
||||
|
||||
def geozone_for(heading)
|
||||
if heading.geozone
|
||||
link_to heading.geozone.name, edit_admin_geozone_path(heading.geozone)
|
||||
else
|
||||
t("geozones.none")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,6 +20,25 @@
|
||||
<%= f.text_field :html_map_coordinates, hint: t("admin.geozones.geozone.coordinates_help") %>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<%= f.text_area :geojson, rows: "10", hint: t("admin.geozones.geozone.geojson_help") %>
|
||||
</div>
|
||||
|
||||
<div class="small-12 large-3 column">
|
||||
<%= f.label :color, nil, for: "color_input", id: "color_input_label" %>
|
||||
<p class="help-text">
|
||||
<%= t("admin.geozones.geozone.color_help", format_help: t("admin.shared.color_help")) %>
|
||||
</p>
|
||||
<div class="row collapse">
|
||||
<div class="small-12 medium-6 column">
|
||||
<%= f.text_field :color, label: false, type: :color %>
|
||||
</div>
|
||||
<div class="small-12 medium-6 column">
|
||||
<%= f.text_field :color, label: false, id: "color_input" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="small-12 column">
|
||||
<%= f.submit(value: t("admin.geozones.edit.form.submit_button"),
|
||||
class: "button success") %>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<th><%= t("admin.geozones.geozone.external_code") %></th>
|
||||
<th><%= t("admin.geozones.geozone.census_code") %></th>
|
||||
<th><%= t("admin.geozones.geozone.coordinates") %></th>
|
||||
<th><%= t("admin.geozones.geozone.geojson") %></th>
|
||||
<th><%= t("admin.actions.actions") %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -19,7 +20,8 @@
|
||||
<td><%= geozone.name %></td>
|
||||
<td><%= geozone.external_code %></td>
|
||||
<td><%= geozone.census_code %></td>
|
||||
<td class="break"><%= geozone.html_map_coordinates %></td>
|
||||
<td><%= yes_no_text(geozone.html_map_coordinates.present?) %></td>
|
||||
<td><%= yes_no_text(geozone.geojson.present?) %></td>
|
||||
<td>
|
||||
<%= render Admin::TableActionsComponent.new(geozone) %>
|
||||
</td>
|
||||
|
||||
@@ -11,4 +11,12 @@ class Admin::Geozones::IndexComponent < ApplicationComponent
|
||||
def title
|
||||
t("admin.geozones.index.title")
|
||||
end
|
||||
|
||||
def yes_no_text(condition)
|
||||
if condition
|
||||
t("shared.yes")
|
||||
else
|
||||
t("shared.no")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="budgets-map">
|
||||
<h2><%= t("budgets.index.map") %></h2>
|
||||
<%= render_map(nil, investments_coordinates: coordinates) %>
|
||||
<%= render_map(nil, investments_coordinates: coordinates, geozones_data: geozones_data) %>
|
||||
</div>
|
||||
|
||||
@@ -21,4 +21,16 @@ class Budgets::MapComponent < ApplicationComponent
|
||||
|
||||
MapLocation.where(investment_id: investments).map(&:json_data)
|
||||
end
|
||||
|
||||
def geozones_data
|
||||
budget.geozones.map do |geozone|
|
||||
{
|
||||
outline_points: geozone.outline_points,
|
||||
color: geozone.color,
|
||||
headings: budget.headings.where(geozone: geozone).map do |heading|
|
||||
link_to heading.name, budget_investments_path(budget, heading_id: heading.id)
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
class Shared::MapLocationComponent < ApplicationComponent
|
||||
attr_reader :investments_coordinates, :form
|
||||
attr_reader :investments_coordinates, :form, :geozones_data
|
||||
|
||||
def initialize(map_location, investments_coordinates: nil, form: nil)
|
||||
def initialize(map_location, investments_coordinates: nil, form: nil, geozones_data: nil)
|
||||
@map_location = map_location
|
||||
@investments_coordinates = investments_coordinates
|
||||
@form = form
|
||||
@geozones_data = geozones_data
|
||||
end
|
||||
|
||||
def map_location
|
||||
@@ -56,7 +57,8 @@ class Shared::MapLocationComponent < ApplicationComponent
|
||||
marker_remove_selector: "##{remove_marker_id}",
|
||||
marker_investments_coordinates: investments_coordinates,
|
||||
marker_latitude: map_location.latitude.presence,
|
||||
marker_longitude: map_location.longitude.presence
|
||||
marker_longitude: map_location.longitude.presence,
|
||||
geozones: geozones_data
|
||||
}.merge(input_selectors)
|
||||
end
|
||||
|
||||
|
||||
@@ -47,6 +47,6 @@ class Admin::GeozonesController < Admin::BaseController
|
||||
end
|
||||
|
||||
def allowed_params
|
||||
[:name, :external_code, :census_code, :html_map_coordinates]
|
||||
[:name, :external_code, :census_code, :html_map_coordinates, :geojson, :color]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -59,7 +59,7 @@ module Admin::BudgetHeadingsActions
|
||||
end
|
||||
|
||||
def allowed_params
|
||||
valid_attributes = [:price, :population, :allow_custom_content, :latitude, :longitude, :max_ballot_lines]
|
||||
valid_attributes = [:price, :population, :allow_custom_content, :latitude, :longitude, :max_ballot_lines, :geozone_id]
|
||||
|
||||
[*valid_attributes, translation_params(Budget::Heading)]
|
||||
end
|
||||
|
||||
@@ -34,6 +34,7 @@ class Budget < ApplicationRecord
|
||||
has_many :ballots, dependent: :destroy
|
||||
has_many :groups, dependent: :destroy
|
||||
has_many :headings, through: :groups
|
||||
has_many :geozones, through: :headings
|
||||
has_many :lines, through: :ballots, class_name: "Budget::Ballot::Line"
|
||||
has_many :phases, class_name: "Budget::Phase"
|
||||
has_many :budget_administrators, dependent: :destroy
|
||||
|
||||
@@ -22,6 +22,7 @@ class Budget
|
||||
end
|
||||
|
||||
belongs_to :group
|
||||
belongs_to :geozone
|
||||
|
||||
has_many :investments
|
||||
has_many :content_blocks
|
||||
|
||||
23
app/models/concerns/geojson_format_validator.rb
Normal file
23
app/models/concerns/geojson_format_validator.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
class GeojsonFormatValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
if value.present?
|
||||
geojson = parse_json(value)
|
||||
|
||||
unless geojson?(geojson)
|
||||
record.errors.add(attribute, :invalid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_json(geojson_data)
|
||||
JSON.parse(geojson_data) rescue nil
|
||||
end
|
||||
|
||||
def geojson?(geojson)
|
||||
return false unless geojson.is_a?(Hash)
|
||||
|
||||
geojson.dig("geometry", "coordinates").is_a?(Array)
|
||||
end
|
||||
end
|
||||
@@ -4,7 +4,9 @@ class Geozone < ApplicationRecord
|
||||
has_many :proposals
|
||||
has_many :debates
|
||||
has_many :users
|
||||
has_many :headings, class_name: "Budget::Heading", dependent: :nullify
|
||||
validates :name, presence: true
|
||||
validates :geojson, geojson_format: true
|
||||
|
||||
scope :public_for_api, -> { all }
|
||||
|
||||
@@ -17,4 +19,28 @@ class Geozone < ApplicationRecord
|
||||
association.klass.where(geozone: self).empty?
|
||||
end
|
||||
end
|
||||
|
||||
def outline_points
|
||||
normalized_coordinates.map { |longlat| [longlat.last, longlat.first] }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def normalized_coordinates
|
||||
if geojson.present?
|
||||
if geojson.match(/"coordinates"\s*:\s*\[{4}/)
|
||||
coordinates.reduce([], :concat).reduce([], :concat)
|
||||
elsif geojson.match(/"coordinates"\s*:\s*\[{3}/)
|
||||
coordinates.reduce([], :concat)
|
||||
else
|
||||
coordinates
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def coordinates
|
||||
JSON.parse(geojson)["geometry"]["coordinates"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -201,7 +201,9 @@ en:
|
||||
geozone:
|
||||
name: Name
|
||||
external_code: "External code (optional)"
|
||||
color: "Color (optional)"
|
||||
census_code: "Census code (optional)"
|
||||
geojson: "GeoJSON data (optional)"
|
||||
html_map_coordinates: "HTML <map> Coordinates (optional)"
|
||||
milestone:
|
||||
status_id: "Current status (optional)"
|
||||
@@ -545,6 +547,10 @@ en:
|
||||
attributes:
|
||||
max_per_day:
|
||||
invalid: "You have reached the maximum number of private messages per day"
|
||||
geozone:
|
||||
attributes:
|
||||
geojson:
|
||||
invalid: "The GeoJSON provided does not follow the correct format. It must follow the \"Polygon\" or \"MultiPolygon\" type format."
|
||||
image:
|
||||
attributes:
|
||||
attachment:
|
||||
|
||||
@@ -198,6 +198,7 @@ en:
|
||||
success_notice: "Heading deleted successfully"
|
||||
unable_notice: "You cannot delete a Heading that has associated investments"
|
||||
form:
|
||||
geozone_info: "When a heading is associated with a geozone, the geozone will appear on this budget's map if the geozone includes GeoJSON data."
|
||||
max_ballot_lines_info: 'Maximum number of projects a user can vote on this heading during the "Voting projects" phase. Only for budgets using approval voting.'
|
||||
population_info: "Budget Heading population field is used for Statistic purposes at the end of the Budget to show for each Heading that represents an area with population what percentage voted. The field is optional so you can leave it empty if it doesn't apply."
|
||||
coordinates_info: "If latitude and longitude are provided, the investments page for this heading will include a map. This map will be centered using those coordinates."
|
||||
@@ -1391,8 +1392,11 @@ en:
|
||||
external_code: External code
|
||||
census_code: Census code
|
||||
code_help: Response code for this geozone on the census API
|
||||
coordinates: Coordinates
|
||||
color_help: "Color of the zone in a budget's map. %{format_help}"
|
||||
coordinates: Coordinates available
|
||||
coordinates_help: Coordinates that will generate a clickable area on an HTML image map
|
||||
geojson: GeoJSON available
|
||||
geojson_help: "Must follow the \"Polygon\" or \"MultiPolygon\" type format; on a budget's map, a polygon based on this data will appear when a heading is associated to this geozone"
|
||||
create:
|
||||
notice: "Geozone created successfully"
|
||||
edit:
|
||||
|
||||
@@ -200,8 +200,10 @@ es:
|
||||
description: "Descripción"
|
||||
geozone:
|
||||
name: Nombre
|
||||
color: "Color (opcional)"
|
||||
external_code: "Código externo (opcional)"
|
||||
census_code: "Código del censo (opcional)"
|
||||
geojson: "Datos GeoJSON (opcional)"
|
||||
html_map_coordinates: "Coordenadas HTML <map> (opcional)"
|
||||
milestone:
|
||||
status_id: "Estado actual (opcional)"
|
||||
@@ -545,6 +547,10 @@ es:
|
||||
attributes:
|
||||
max_per_day:
|
||||
invalid: "Has llegado al número máximo de mensajes privados por día"
|
||||
geozone:
|
||||
attributes:
|
||||
geojson:
|
||||
invalid: "Los datos GeoJSON proporcionados no tienen el formato correcto. Deben tener un tipo del formato \"Polygon\" o \"MultiPolygon\"."
|
||||
image:
|
||||
attributes:
|
||||
attachment:
|
||||
|
||||
@@ -198,6 +198,7 @@ es:
|
||||
success_notice: "Partida presupuestaria eliminada correctamente"
|
||||
unable_notice: "No se puede eliminar una partida presupuestaria con proyectos asociados"
|
||||
form:
|
||||
geozone_info: "Cuando se asocia una partida a una zona, la zona aparecerá en el mapa de este presupuesto si la zona incluye datos de GeoJSON."
|
||||
max_ballot_lines_info: 'Máximo número de proyectos que un usuario puede votar en esta partida durante la fase "Votación final". Solamente se aplica a presupuestos con votación por aprobación.'
|
||||
population_info: "El campo población de las partidas presupuestarias se usa con fines estadísticos únicamente, con el objetivo de mostrar el porcentaje de votos habidos en cada partida que represente un área con población. Es un campo opcional, así que puedes dejarlo en blanco si no aplica."
|
||||
coordinates_info: "Si se añaden los campos latitud y longitud, en la página de proyectos de esta partida aparecerá un mapa, que estará centrado en esas coordenadas."
|
||||
@@ -1391,8 +1392,11 @@ es:
|
||||
external_code: Código externo
|
||||
census_code: Código del censo
|
||||
code_help: Código de respuesta para esta zona en la API del censo
|
||||
coordinates: Coordenadas
|
||||
color_help: "Color con el que aparecerá esta zona en el mapa de un presupuesto. %{format_help}"
|
||||
coordinates: Coordenadas disponibles
|
||||
coordinates_help: Coordenadas que generarán una zona clicable en un mapa de imagen HTML
|
||||
geojson: GeoJSON disponible
|
||||
geojson_help: "Deben tener un tipo del formato \"Polygon\" o \"MultiPolygon\"; en el mapa de un presupuesto aparecerá un polígono basado en estos datos si una partida se asocia a esta geozona"
|
||||
create:
|
||||
notice: "Zona creada correctamente"
|
||||
edit:
|
||||
|
||||
8
db/migrate/20181113184434_add_polygons_to_geozones.rb
Normal file
8
db/migrate/20181113184434_add_polygons_to_geozones.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
class AddPolygonsToGeozones < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
change_table :geozones do |t|
|
||||
t.text :geojson
|
||||
t.string :color
|
||||
end
|
||||
end
|
||||
end
|
||||
5
db/migrate/20190109010131_add_geozone_to_headings.rb
Normal file
5
db/migrate/20190109010131_add_geozone_to_headings.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddGeozoneToHeadings < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
add_reference :budget_headings, :geozone, index: true, foreign_key: true
|
||||
end
|
||||
end
|
||||
@@ -238,9 +238,11 @@ ActiveRecord::Schema.define(version: 2023_05_23_090028) do
|
||||
t.boolean "allow_custom_content", default: false
|
||||
t.text "latitude"
|
||||
t.text "longitude"
|
||||
t.integer "geozone_id"
|
||||
t.integer "max_ballot_lines", default: 1
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.index ["geozone_id"], name: "index_budget_headings_on_geozone_id"
|
||||
t.index ["group_id"], name: "index_budget_headings_on_group_id"
|
||||
end
|
||||
|
||||
@@ -635,6 +637,8 @@ ActiveRecord::Schema.define(version: 2023_05_23_090028) do
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "census_code"
|
||||
t.text "geojson"
|
||||
t.string "color"
|
||||
end
|
||||
|
||||
create_table "geozones_polls", id: :serial, force: :cascade do |t|
|
||||
@@ -1775,6 +1779,7 @@ ActiveRecord::Schema.define(version: 2023_05_23_090028) do
|
||||
add_foreign_key "administrators", "users"
|
||||
add_foreign_key "budget_administrators", "administrators"
|
||||
add_foreign_key "budget_administrators", "budgets"
|
||||
add_foreign_key "budget_headings", "geozones"
|
||||
add_foreign_key "budget_investments", "communities"
|
||||
add_foreign_key "budget_valuators", "budgets"
|
||||
add_foreign_key "budget_valuators", "valuators"
|
||||
|
||||
32
spec/components/admin/budget_headings/form_component_spec.rb
Normal file
32
spec/components/admin/budget_headings/form_component_spec.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
require "rails_helper"
|
||||
|
||||
describe Admin::BudgetHeadings::FormComponent do
|
||||
describe "geozone field" do
|
||||
let(:heading) { create(:budget_heading) }
|
||||
let(:component) { Admin::BudgetHeadings::FormComponent.new(heading, path: "/", action: nil) }
|
||||
before { Setting["feature.map"] = true }
|
||||
|
||||
it "is shown when the map feature is enabled" do
|
||||
render_inline component
|
||||
|
||||
expect(page).to have_select "Scope of operation"
|
||||
end
|
||||
|
||||
it "is not shown when the map feature is disabled" do
|
||||
Setting["feature.map"] = false
|
||||
|
||||
render_inline component
|
||||
|
||||
expect(page).not_to have_select "Scope of operation"
|
||||
end
|
||||
|
||||
it "includes all existing geozones plus an option for all city" do
|
||||
create(:geozone, name: "Under the sea")
|
||||
create(:geozone, name: "Above the skies")
|
||||
|
||||
render_inline component
|
||||
|
||||
expect(page).to have_select "Scope of operation", options: ["All city", "Under the sea", "Above the skies"]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
require "rails_helper"
|
||||
|
||||
describe Admin::BudgetHeadings::HeadingsComponent do
|
||||
describe Admin::BudgetHeadings::HeadingsComponent, controller: Admin::BaseController do
|
||||
it "includes group name in the message when there are no headings" do
|
||||
group = create(:budget_group, name: "Whole planet")
|
||||
|
||||
@@ -9,4 +9,22 @@ describe Admin::BudgetHeadings::HeadingsComponent do
|
||||
expect(page.text.strip).to eq "There are no headings in the Whole planet group."
|
||||
expect(page).to have_css "strong", exact_text: "Whole planet"
|
||||
end
|
||||
|
||||
describe "#geozone_for" do
|
||||
it "shows the geozone associated to the heading" do
|
||||
heading = create(:budget_heading, name: "Local", geozone: create(:geozone, name: "Here"))
|
||||
|
||||
render_inline Admin::BudgetHeadings::HeadingsComponent.new(heading.group.headings)
|
||||
|
||||
expect(page.find("tr", text: "Local")).to have_content "Here"
|
||||
end
|
||||
|
||||
it "shows a generic location for headings with no associated geozone" do
|
||||
heading = create(:budget_heading, name: "Universal", geozone: nil)
|
||||
|
||||
render_inline Admin::BudgetHeadings::HeadingsComponent.new(heading.group.headings)
|
||||
|
||||
expect(page.find("tr", text: "Universal")).to have_content "All city"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
23
spec/components/admin/geozones/index_component_spec.rb
Normal file
23
spec/components/admin/geozones/index_component_spec.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
require "rails_helper"
|
||||
|
||||
describe Admin::Geozones::IndexComponent, controller: Admin::BaseController do
|
||||
describe "Coordinates description" do
|
||||
it "includes whether coordinates are defined or not" do
|
||||
geozones = [
|
||||
create(:geozone, :with_geojson, name: "GeoJSON", external_code: "1", census_code: "2"),
|
||||
create(:geozone, :with_html_coordinates, name: "HTML", external_code: "3", census_code: "4"),
|
||||
create(:geozone, :with_geojson, :with_html_coordinates, name: "With both", external_code: "6", census_code: "7"),
|
||||
create(:geozone, name: "With none", external_code: "8", census_code: "9")
|
||||
]
|
||||
|
||||
render_inline Admin::Geozones::IndexComponent.new(geozones)
|
||||
|
||||
expect(page).to have_table with_rows: [
|
||||
["GeoJSON", "1", "2", "No", "Yes", "Edit Delete"],
|
||||
["HTML", "3", "4", "Yes", "No", "Edit Delete"],
|
||||
["With both", "6", "7", "Yes", "Yes", "Edit Delete"],
|
||||
["With none", "8", "9", "No", "No", "Edit Delete"]
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,11 +1,13 @@
|
||||
require "rails_helper"
|
||||
|
||||
describe Budgets::MapComponent do
|
||||
let(:budget) { build(:budget) }
|
||||
before { Setting["feature.map"] = true }
|
||||
let(:budget) { create(:budget, :accepting) }
|
||||
|
||||
describe "#render?" do
|
||||
let(:budget) { build(:budget) }
|
||||
|
||||
it "is rendered after the informing phase when the map feature is enabled" do
|
||||
Setting["feature.map"] = true
|
||||
budget.phase = "accepting"
|
||||
|
||||
render_inline Budgets::MapComponent.new(budget)
|
||||
@@ -14,7 +16,6 @@ describe Budgets::MapComponent do
|
||||
end
|
||||
|
||||
it "is not rendered during the informing phase" do
|
||||
Setting["feature.map"] = true
|
||||
budget.phase = "informing"
|
||||
|
||||
render_inline Budgets::MapComponent.new(budget)
|
||||
@@ -31,4 +32,25 @@ describe Budgets::MapComponent do
|
||||
expect(page).not_to be_rendered
|
||||
end
|
||||
end
|
||||
|
||||
describe "#geozones_data" do
|
||||
it "renders data for the geozones associated with the budget" do
|
||||
create(:budget_heading, geozone: create(:geozone, color: "#0000ff"), budget: budget)
|
||||
create(:budget_heading, geozone: create(:geozone, color: "#ff0000"), budget: create(:budget))
|
||||
|
||||
render_inline Budgets::MapComponent.new(budget)
|
||||
|
||||
expect(page).to have_css "[data-geozones*='#0000ff']"
|
||||
expect(page).not_to have_css "[data-geozones*='#ff0000']"
|
||||
end
|
||||
|
||||
it "renders empty geozone data when there are no geozones" do
|
||||
create(:budget_heading, geozone: nil, budget: budget)
|
||||
create(:budget_heading, geozone: create(:geozone, color: "#ff0000"), budget: create(:budget))
|
||||
|
||||
render_inline Budgets::MapComponent.new(budget)
|
||||
|
||||
expect(page).to have_css "[data-geozones='[]']"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,10 +8,19 @@ FactoryBot.define do
|
||||
sequence(:name) { |n| "District #{n}" }
|
||||
sequence(:external_code, &:to_s)
|
||||
sequence(:census_code, &:to_s)
|
||||
color { "#0081aa" }
|
||||
|
||||
trait :in_census do
|
||||
census_code { "01" }
|
||||
end
|
||||
|
||||
trait :with_html_coordinates do
|
||||
html_map_coordinates { "30,139,45,153,77,148,107,165" }
|
||||
end
|
||||
|
||||
trait :with_geojson do
|
||||
geojson { '{ "geometry": { "type": "Polygon", "coordinates": [[-0.117,51.513],[-0.118,51.512],[-0.119,51.514]] } }' }
|
||||
end
|
||||
end
|
||||
|
||||
factory :banner do
|
||||
|
||||
@@ -12,6 +12,19 @@ describe Geozone do
|
||||
expect(geozone).not_to be_valid
|
||||
end
|
||||
|
||||
it "is valid without geojson" do
|
||||
geozone.geojson = nil
|
||||
expect(geozone).to be_valid
|
||||
end
|
||||
|
||||
it "is not valid with invalid geojson file format" do
|
||||
geozone.geojson = '{"geo\":{"type":"Incorrect key","coordinates": [
|
||||
[40.8792937308316, -3.9259027239257],
|
||||
[40.8788966596619, -3.9249047078766],
|
||||
[40.8789131852224, -3.9247799675785]]}}'
|
||||
expect(geozone).not_to be_valid
|
||||
end
|
||||
|
||||
describe "#safe_to_destroy?" do
|
||||
let(:geozone) { create(:geozone) }
|
||||
|
||||
@@ -33,5 +46,34 @@ describe Geozone do
|
||||
create(:debate, geozone: geozone)
|
||||
expect(geozone).not_to be_safe_to_destroy
|
||||
end
|
||||
|
||||
it "is false when already linked to a heading" do
|
||||
create(:budget_heading, geozone: geozone)
|
||||
expect(geozone).not_to be_safe_to_destroy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#outline_points" do
|
||||
it "returns empty array when geojson is nil" do
|
||||
expect(geozone.outline_points).to eq([])
|
||||
end
|
||||
|
||||
it "returns coordinates array when geojson is not nil" do
|
||||
geozone = build(:geozone, geojson: '{
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[40.8792937308316, -3.9259027239257],
|
||||
[40.8788966596619, -3.9249047078766],
|
||||
[40.8789131852224, -3.9247799675785]
|
||||
]
|
||||
}
|
||||
}')
|
||||
|
||||
expect(geozone.outline_points).to eq(
|
||||
[[-3.9259027239257, 40.8792937308316],
|
||||
[-3.9249047078766, 40.8788966596619],
|
||||
[-3.9247799675785, 40.8789131852224]])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -112,14 +112,11 @@ describe "Budgets creation wizard", :admin do
|
||||
click_link "Finish"
|
||||
|
||||
within "section", text: "Heading groups" do
|
||||
within "section", text: "All city" do
|
||||
within_table "Headings in All city" do
|
||||
expect(page).to have_css "tbody tr", count: 1
|
||||
expect(page).to have_content "All city"
|
||||
end
|
||||
end
|
||||
|
||||
within "section", text: "Districts" do
|
||||
within_table "Headings in Districts" do
|
||||
expect(page).to have_css "tbody tr", count: 2
|
||||
expect(page).to have_content "North"
|
||||
@@ -127,5 +124,4 @@ describe "Budgets creation wizard", :admin do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -107,4 +107,39 @@ describe "Admin geozones", :admin do
|
||||
expect(page).to have_content "Delete me!"
|
||||
end
|
||||
end
|
||||
|
||||
scenario "Show polygons when a heading is associated with a geozone" do
|
||||
Setting["feature.map"] = true
|
||||
|
||||
geojson = '{ "geometry": { "type": "Polygon", "coordinates": [[-0.1,51.5],[-0.2,51.4],[-0.3,51.6]] } }'
|
||||
geozone = create(:geozone, name: "Polygon me!")
|
||||
budget = create(:budget)
|
||||
group = create(:budget_group, budget: budget)
|
||||
heading = create(:budget_heading, name: "Area 51", group: group)
|
||||
|
||||
visit edit_admin_geozone_path(geozone)
|
||||
fill_in "GeoJSON data (optional)", with: geojson
|
||||
fill_in "Color (optional)", with: "#f5c211"
|
||||
click_button "Save changes"
|
||||
|
||||
expect(page).to have_content "Geozone updated successfully"
|
||||
|
||||
visit edit_admin_budget_group_heading_path(budget, group, heading)
|
||||
select "Polygon me!", from: "Scope of operation"
|
||||
|
||||
click_button "Save heading"
|
||||
|
||||
expect(page).to have_content "Heading updated successfully"
|
||||
|
||||
visit budget_path(budget)
|
||||
|
||||
expect(page).to have_css ".map-polygon[fill='#f5c211']"
|
||||
within(".map-location") { expect(page).not_to have_link "Area 51" }
|
||||
|
||||
find(".map-polygon").click
|
||||
|
||||
within ".map-location" do
|
||||
expect(page).to have_link "Area 51", href: budget_investments_path(budget, heading_id: heading.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user