Skip to content
Snippets Groups Projects
Commit 04c6c285 authored by Kerdo Kurs's avatar Kerdo Kurs
Browse files

implement BDD #28

parent 818ebcbb
No related branches found
No related tags found
1 merge request!31Implement searching, storing and revisiting searches
......@@ -68,4 +68,9 @@ defmodule WhiteBreadConfig do
suite name: "FR-16: Mark Property as Not Interested",
context: NotInterestedPropertiesContext,
feature_paths: ["features/not_interested_properties.feature"]
suite name: "FR-13, FR-19 & FR-20: Property Search and Revisit",
context: PropertySearchContext,
feature_paths: ["features/property_search.feature"]
end
defmodule PropertySearchContext do
use WhiteBread.Context
use Hound.Helpers
alias PropTrackr.Accounts
alias PropTrackr.Repo
alias PropTrackr.Accounts.User
alias PropTrackr.Properties.Property
scenario_starting_state fn _state ->
Ecto.Adapters.SQL.Sandbox.checkout(PropTrackr.Repo)
Ecto.Adapters.SQL.Sandbox.mode(PropTrackr.Repo, {:shared, self()})
Hound.start_session()
%{}
end
scenario_finalize fn _status, _state ->
Ecto.Adapters.SQL.Sandbox.checkin(PropTrackr.Repo)
Hound.end_session()
end
given_ ~r/^there exists following accounts$/, fn state, %{ table_data: table } ->
table
|> Enum.map(fn user_details -> User.changeset(%User{}, user_details) end)
|> Enum.each(fn changeset -> Repo.insert!(changeset) end)
existing_user = List.first(table)
{
:ok,
state
|> Map.put(:email, existing_user[:email])
}
end
given_ ~r/^the following properties exist$/, fn state, %{ table_data: table } ->
owner = Repo.get_by(User, email: state[:email])
advertisements =
table
|> Enum.map(fn details -> details |> Map.put(:reference, Ecto.UUID.generate()) end)
|> Enum.map(fn details -> {Ecto.build_assoc(owner, :properties, details), details} end)
|> Enum.map(fn {assoc, details} -> Property.changeset(assoc, details) end)
|> Enum.map(fn changeset -> Repo.insert!(changeset) end)
countries = Enum.map(advertisements, fn advertisement -> advertisement.location end)
|> Enum.map(fn location -> String.split(location, ",") end)
|> Enum.map(fn location -> List.last(location) end)
|> Enum.map(fn location -> String.trim(location) end)
|> Enum.uniq()
areas = Enum.map(advertisements, fn advertisement -> advertisement.location end)
|> Enum.map(fn location -> String.split(location, ",") end)
|> Enum.map(fn location -> List.first(location) end)
|> Enum.map(fn location -> String.trim(location) end)
|> Enum.uniq()
{
:ok,
state
|> Map.put(:advertisements, advertisements)
|> Map.put(:countries, countries)
|> Map.put(:areas, areas)
}
end
and_ ~r/^I want to perform a search$/, fn state ->
navigate_to("/")
click({:id, "search"})
{:ok, state}
end
then_ ~r/^I should see all countries of the properties in the filter options$/, fn state ->
countries = state[:countries]
Enum.each(countries, fn country ->
assert find_element(:css, "option[value='#{country}']")
end)
{:ok, state}
end
and_ ~r/^I should see all cities of the properties in the filter options$/, fn state ->
areas = state[:areas]
Enum.each(areas, fn area ->
assert find_element(:css, "option[value='#{area}']")
end)
{:ok, state}
end
and_ ~r/^I should see a max price filter with the max price of the properties$/, fn state ->
advertisements = state[:advertisements]
prices = Enum.map(advertisements, fn advertisement -> advertisement.price end)
max_price = Enum.max(prices)
assert visible_in_page? ~r/#{max_price}/
max_price_element = find_element(:css, "input[id='max_price']")
assert max_price_element
{:ok, state}
end
and_ ~r/^I should see a min price filter with the min price of the properties$/, fn state ->
advertisements = state[:advertisements]
prices = Enum.map(advertisements, fn advertisement -> advertisement.price end)
min_price = Enum.min(prices)
assert visible_in_page? ~r/#{min_price}/
min_price_element = find_element(:css, "input[id='min_price']")
assert min_price_element
{:ok, state}
end
and_ ~r/^I should see a max room filter with the room count of the properties$/, fn state ->
advertisements = state[:advertisements]
rooms = Enum.map(advertisements, fn advertisement -> advertisement.room_count end)
max_rooms = Enum.max(rooms)
assert visible_in_page? ~r/#{max_rooms}/
max_rooms_element = find_element(:css, "input[id='max_rooms']")
assert max_rooms_element
{:ok, state}
end
and_ ~r/^I should see a min room filter with the room count of the properties$/, fn state ->
advertisements = state[:advertisements]
rooms = Enum.map(advertisements, fn advertisement -> advertisement.room_count end)
min_rooms = Enum.max(rooms)
assert visible_in_page? ~r/#{min_rooms}/
min_rooms_element = find_element(:css, "input[id='min_rooms']")
assert min_rooms_element
{:ok, state}
end
when_ ~r/^I click on the search button$/, fn state ->
click({:id, "search_button"})
{:ok, state}
end
then_ ~r/^I should see "(?<count>[^"]+)" properties$/, fn state, %{ count: count } ->
el_count = length(find_all_elements(:class, "property-card"))
IO.inspect(el_count)
assert el_count == String.to_integer(count)
{:ok, state}
end
end
Feature: FR-13, FR-19 & FR-20: Property Search and Revisit
Scenario: User should see filter options and boundaries for search
Given there exists following accounts
| name | surname | birth_date | phone_number | email | password | confirm_password |
| Existing | Account | 2000-01-01 | 000 | existing.account@gmail.com | password | password |
And the following properties exist
| title | description | type | property_type | state | location | room_count | area | floor | floor_count | price |
| Rent property cheap | This is a really cool property | rent | apartment | available | London, UK | 1 | 90.0 | 2 | 8 | 500 |
| Rent property | Also a really cool property | rent | apartment | available | London, UK | 3 | 160.0 | 2 | 5 | 6000 |
| Rent property Tartu | Also a really cool property | rent | apartment | available | Tartu, Estonia | 2 | 120.0 | 1 | 2 | 800 |
| Buy property Tartu | Also a really cool property | sell | apartment | available | Tartu, Estonia | 2 | 120.0 | 1 | 2 | 800000 |
And I want to perform a search
Then I should see all countries of the properties in the filter options
And I should see all cities of the properties in the filter options
And I should see a max price filter with the max price of the properties
And I should see a min price filter with the min price of the properties
And I should see a max room filter with the room count of the properties
And I should see a min room filter with the room count of the properties
Scenario: User should be able to search with default values
Given there exists following accounts
| name | surname | birth_date | phone_number | email | password | confirm_password |
| Existing | Account | 2000-01-01 | 000 | existing.account@gmail.com | password | password |
And the following properties exist
| title | description | type | property_type | state | location | room_count | area | floor | floor_count | price |
| Rent property cheap | This is a really cool property | rent | apartment | available | London, UK | 1 | 90.0 | 2 | 8 | 500 |
| Rent property | Also a really cool property | rent | apartment | available | London, UK | 3 | 160.0 | 2 | 5 | 6000 |
| Rent property Tartu | Also a really cool property | rent | apartment | available | Tartu, Estonia | 2 | 120.0 | 1 | 2 | 800 |
| Buy property Tartu | Also a really cool property | sell | apartment | available | Tartu, Estonia | 2 | 120.0 | 1 | 2 | 800000 |
And I want to perform a search
When I click on the search button
Then I should see "3" properties
......@@ -24,8 +24,17 @@ defmodule PropTrackr.Search do
end
def create_query(struct, current_user) do
areas = struct.areas
locations = Enum.map(areas, fn area -> area <> ", " <> struct.location end)
areas = if Map.has_key?(struct, :areas) do
struct.areas
else
[]
end
locations = if areas == [] do
[]
else
Enum.map(areas, fn area -> area <> ", " <> struct.location end)
end
current_user_id = if current_user != nil do
current_user.id
......@@ -38,7 +47,6 @@ defmodule PropTrackr.Search do
left_join: ni in NotInterested,
on: ni.property_id == p.id and ni.user_id == ^(current_user_id || 0),
order_by: [asc: not is_nil(ni.id), asc: p.state, asc: p.inserted_at],
where: p.location in ^locations,
where: p.price >= ^struct.min_price,
where: p.price <= ^struct.max_price,
where: p.room_count >= ^struct.min_rooms,
......@@ -48,6 +56,12 @@ defmodule PropTrackr.Search do
select: p,
)
query = if locations == [] do
query
else
query |> where([p], p.location in ^locations)
end
query = if struct.type == :any do
query
else
......
......@@ -6,19 +6,23 @@
<% end %>
</.header>
<%= if not @is_search and @conn.assigns[:current_user] do %>
<%= if not @is_search or @conn.assigns[:current_user] do %>
<div class="flex flex-row justify-end gap-x-2">
<.link href={~p"/properties/new"}>
<.button type="button" class="text-white rounded px-4 py-2" id="add_advertisement">
Add a property
</.button>
</.link>
<%= if @conn.assigns[:current_user] do %>
<.link href={~p"/properties/new"}>
<.button type="button" class="text-white rounded px-4 py-2" id="add_advertisement">
Add a property
</.button>
</.link>
<% end %>
<.link href={~p"/search"}>
<.button type="button" class="text-white rounded px-4 py-2" id="search">
Search
</.button>
</.link>
<%= if not @is_search do %>
<.link href={~p"/search"}>
<.button type="button" class="text-white rounded px-4 py-2" id="search">
Search
</.button>
</.link>
<% end %>
</div>
<% end %>
......@@ -34,7 +38,7 @@
<div id="properties" class="flex flex-col gap-y-4 mt-20">
<%= for property <- @properties do %>
<div class="bg-white border-black border rounded px-4 py-2">
<div class="bg-white border-black border rounded px-4 py-2 property-card">
<div class="flex gap-4">
<div class="w-48 h-48 flex-shrink-0">
<%= if first_photo = Enum.at(property.photos, 0) do %>
......
......@@ -37,6 +37,7 @@
type="select"
label="Country"
options={@countries}
value={List.first(@countries)}
required
/>
......@@ -47,17 +48,16 @@
label="City (Area)"
options={@cities}
multiple={true}
required
/>
<.input field={f[:min_price]} id="min_price" type="range" label="Min price" value={@min_price} min={@min_price} max={@max_price} step="10" oninput="document.getElementById('min_price_value').innerText = this.value" required />
<span id="min_price_value"><%= @min_price %></span>
<.input field={f[:max_price]} id="min_price" type="range" label="Max price" value={@max_price} min={@min_price} max={@max_price} step="10" oninput="document.getElementById('max_price_value').innerText = this.value" required />
<.input field={f[:max_price]} id="max_price" type="range" label="Max price" value={@max_price} min={@min_price} max={@max_price} step="10" oninput="document.getElementById('max_price_value').innerText = this.value" required />
<span id="max_price_value"><%= @max_price %></span>
<.input field={f[:min_rooms]} id="min_room"s type="range" label="Min rooms" value={@min_rooms} min={@min_rooms} max={@max_rooms} step="1" oninput="document.getElementById('min_rooms_value').innerText = this.value" required />
<.input field={f[:min_rooms]} id="min_rooms" type="range" label="Min rooms" value={@min_rooms} min={@min_rooms} max={@max_rooms} step="1" oninput="document.getElementById('min_rooms_value').innerText = this.value" required />
<span id="min_rooms_value"><%= @min_rooms %></span>
<.input field={f[:max_rooms]} id="max_room"s type="range" label="Max rooms" value={@max_rooms} min={@min_rooms} max={@max_rooms} step="1" oninput="document.getElementById('max_rooms_value').innerText = this.value" required />
<.input field={f[:max_rooms]} id="max_rooms" type="range" label="Max rooms" value={@max_rooms} min={@min_rooms} max={@max_rooms} step="1" oninput="document.getElementById('max_rooms_value').innerText = this.value" required />
<span id="max_rooms_value"><%= @max_rooms %></span>
<:actions>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment