Skip to content

Commit

Permalink
Feat: Infinite scroll (#15)
Browse files Browse the repository at this point in the history
* feat: hook for infinit scroll

* feat: add tests for infinite scroll hook
  • Loading branch information
ricksonoliveira authored Dec 13, 2023
1 parent 7fa2788 commit c4e0cd7
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 10 deletions.
4 changes: 3 additions & 1 deletion assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ import "phoenix_html"
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
import Hooks from "./hooks"

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})

let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks: Hooks})

// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
Expand Down
7 changes: 7 additions & 0 deletions assets/js/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import LoadMoreProducts from "./hooks/loadMoreProducts"

let Hooks = {
LoadMoreProducts: LoadMoreProducts
}

export default Hooks
17 changes: 17 additions & 0 deletions assets/js/hooks/loadMoreProducts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const LoadMoreProducts = {
mounted() {
// console.log(this.el.dataset.page)
const selector = "#" + this.el.id;
this.observer = new IntersectionObserver((entries) => {
const entry = entries[0]
if (entry.isIntersecting) {
console.log("load more")
this.pushEventTo(selector, "load_more_products", {})
}
})

this.observer.observe(this.el)
}
}

export default LoadMoreProducts;
1 change: 1 addition & 0 deletions lib/food_order_web/live/page_live/item/item.html.heex
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<div
id={@id}
data-role="item"
data-id={@product.id}
class="shadow-lg rounded-lg p-4 mb-2 hover:bg-neutral-100"
Expand Down
23 changes: 22 additions & 1 deletion lib/food_order_web/live/page_live/page_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@ defmodule FoodOrderWeb.PageLive do
alias FoodOrderWeb.PageLive.Item

def mount(_, _, socket) do
{:ok, assign(socket, products: Products.list_products())}
socket =
socket
|> assign(page: 1, per_page: 8)
|> assign_list_products()

{:ok, socket}
end

def handle_event("load_more_products", _params, socket) do
socket =
socket
|> update(:page, &(&1 + 1))
|> assign_list_products()

{:noreply, socket}
end

defp assign_list_products(socket) do
page = socket.assigns.page
per_page = socket.assigns.per_page
paginate = [{:paginate, %{page: page, per_page: per_page}}]
assign(socket, products: Products.list_products(paginate))
end
end
5 changes: 5 additions & 0 deletions lib/food_order_web/live/page_live/page_live.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
<div
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8"
data-role="products-list"
id="products-list"
phx-update="append"
>
<.live_component :for={product <- @products} module={Item}, id={product.id} product={product} />
</div>

</section>

<div id="load_more_products" phx-hook="LoadMoreProducts"></div>
58 changes: 50 additions & 8 deletions test/food_order_web/live/page_live/page_live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ defmodule FoodOrderWeb.PageLiveTest do
import Phoenix.LiveViewTest
import FoodOrder.ProductsFixtures

setup %{conn: conn} do
product = product_fixture()
test "load main hero html", %{conn: conn} do
_product = product_fixture()
{:ok, view, _html} = live(conn, ~p"/")

{:ok, %{conn: conn, view: view, product: product}}
end

test "load main hero html", %{view: view} do
hero_cta = "[data-role=hero-cta]"
assert has_element?(view, "[data-role=hero]")
assert has_element?(view, hero_cta)
Expand All @@ -22,13 +18,19 @@ defmodule FoodOrderWeb.PageLiveTest do
assert has_element?(view, "[data-role=hero-img]")
end

test "load products html", %{view: view} do
test "load products html", %{conn: conn} do
_product = product_fixture()
{:ok, view, _html} = live(conn, ~p"/")

assert has_element?(view, "[data-role=products-section]")
assert has_element?(view, "[data-role=products-section]>h1", "All foods")
assert has_element?(view, "[data-role=products-list]")
end

test "load main item elements", %{view: view, product: product} do
test "load main item elements", %{conn: conn} do
product = product_fixture()
{:ok, view, _html} = live(conn, ~p"/")

assert has_element?(view, "[data-role=item][data-id=#{product.id}]")
assert has_element?(view, "[data-role=item][data-id=#{product.id}]>img")
assert has_element?(view, "[data-role=item-details][data-id=#{product.id}]>h2", product.name)
Expand All @@ -49,4 +51,44 @@ defmodule FoodOrderWeb.PageLiveTest do
|> element("[data-role=item-details][data-id=#{product.id}]>div>button")
|> render() =~ "Add"
end

test "infinite scroll loads more products when triggered", %{conn: conn} do
products = for _ <- 0..12, do: product_fixture()

{:ok, lv, _html} = live(conn, ~p"/")

# We need to split the products into two pages, because the first page
# is already loaded when the live view is mounted.
[product_page_1, product_page_2] = Enum.chunk_every(products, 8)

# Assert that the first page of products is loaded.
Enum.each(product_page_1, fn product ->
assert has_element?(lv, "[data-role=item][data-id=#{product.id}]")
end)

# Assert that the second page of products is not loaded.
Enum.each(product_page_2, fn product ->
refute has_element?(lv, "[data-role=item][data-id=#{product.id}]")
end)

# Trigger the load more products hook event.
# Trigger twice to make sure that the hook is executed.
lv
|> element("#load_more_products")
|> render_hook("load_more_products", %{})

lv
|> element("#load_more_products")
|> render_hook("load_more_products", %{})

# Assert that the second page of products is loaded.
Enum.each(product_page_2, fn product ->
assert has_element?(lv, "[data-role=item][data-id=#{product.id}]")
end)

# Assert that the first page of products is still loaded as well.
Enum.each(product_page_1, fn product ->
assert has_element?(lv, "[data-role=item][data-id=#{product.id}]")
end)
end
end

0 comments on commit c4e0cd7

Please sign in to comment.