mirror of
https://github.com/woutdp/live_svelte
synced 2026-05-24 09:28:21 +00:00
added drag and drop example
This commit is contained in:
parent
c8cc0b91ec
commit
7a5baead81
11 changed files with 365 additions and 1826 deletions
1862
example_project/assets/package-lock.json
generated
1862
example_project/assets/package-lock.json
generated
File diff suppressed because it is too large
Load diff
36
example_project/assets/svelte/DragDrop.svelte
Normal file
36
example_project/assets/svelte/DragDrop.svelte
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<script lang="ts">
|
||||
import {dndzone} from "svelte-dnd-action"
|
||||
|
||||
let {items, live} = $props()
|
||||
let localItems = $derived(items)
|
||||
|
||||
function handleConsider(e) {
|
||||
localItems = e.detail.items
|
||||
}
|
||||
|
||||
function handleFinalize(e) {
|
||||
localItems = e.detail.items
|
||||
live.pushEvent("reorder", {ids: localItems.map(i => i.id)})
|
||||
}
|
||||
$inspect(items)
|
||||
</script>
|
||||
|
||||
<div use:dndzone={{items: localItems}} onconsider={handleConsider} onfinalize={handleFinalize} class="flex flex-col gap-2">
|
||||
{#each localItems as item (item.id)}
|
||||
<div data-testid="drag-item" class="card bg-base-200 border border-base-300 cursor-grab active:cursor-grabbing select-none">
|
||||
<div class="card-body py-3 px-4 flex-row items-center gap-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-4 h-4 text-base-content/30 shrink-0"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M8 6a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm0 6a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm0 6a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm8-12a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm0 6a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm0 6a2 2 0 1 1-4 0 2 2 0 0 1 4 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span data-testid="drag-item-name" class="text-sm font-medium">{item.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
@ -23,6 +23,7 @@ defmodule ExampleWeb.Layouts do
|
|||
%{label: "Plus/Minus (Static)", to: ~p"/plus-minus-svelte"},
|
||||
%{label: "Plus/Minus (Live)", to: ~p"/live-plus-minus"},
|
||||
%{label: "Hybrid Counter", to: ~p"/live-plus-minus-hybrid"},
|
||||
%{label: "Drag & Drop", to: ~p"/live-drag-drop"},
|
||||
%{label: "Static + List", to: ~p"/live-static-color"}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -85,6 +85,12 @@
|
|||
</a>
|
||||
<span class="text-base-content/50 text-sm">- Svelte component with dynamic list</span>
|
||||
</li>
|
||||
<li>
|
||||
<a href={~p"/live-drag-drop"} class="link link-primary">
|
||||
Drag & Drop
|
||||
</a>
|
||||
<span class="text-base-content/50 text-sm">- Svelte component with drag & drop</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
60
example_project/lib/example_web/live/live_drag_drop.ex
Normal file
60
example_project/lib/example_web/live/live_drag_drop.ex
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
defmodule ExampleWeb.LiveDragDrop do
|
||||
use ExampleWeb, :live_view
|
||||
|
||||
@initial_items [
|
||||
%{id: 1, name: "Design mockups"},
|
||||
%{id: 2, name: "Set up database"},
|
||||
%{id: 3, name: "Write API endpoints"},
|
||||
%{id: 4, name: "Build frontend"},
|
||||
%{id: 5, name: "Write tests"},
|
||||
%{id: 6, name: "Deploy to production"}
|
||||
]
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, assign(socket, items: @initial_items)}
|
||||
end
|
||||
|
||||
def handle_event("reorder", %{"ids" => ids}, socket) do
|
||||
ordered =
|
||||
Enum.map(ids, fn id ->
|
||||
Enum.find(socket.assigns.items, &(&1.id == id))
|
||||
end)
|
||||
|
||||
{:noreply, assign(socket, items: ordered)}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="min-h-screen bg-base-200/40 py-8 px-4">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<h1 class="text-center text-2xl font-light my-4">Drag & Drop Demo</h1>
|
||||
<p class="text-sm text-base-content/50 mb-8 text-center">
|
||||
Reorder tasks with drag and drop. The new order is synced to the server via pushEvent.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col gap-8">
|
||||
<section class="card bg-base-100 shadow-lg border border-base-300/50">
|
||||
<div class="card-body gap-4">
|
||||
<span class="badge badge-outline badge-sm font-medium text-base-content/70 w-fit">
|
||||
LiveSvelte
|
||||
</span>
|
||||
<.svelte name="DragDrop" props={%{items: @items}} socket={@socket} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card bg-base-100 shadow-lg border border-base-300/50">
|
||||
<div class="card-body gap-4">
|
||||
<span class="badge badge-outline badge-sm font-medium text-base-content/70 w-fit">
|
||||
Server order
|
||||
</span>
|
||||
<ol data-testid="server-order-list" class="list-decimal list-inside space-y-1 text-sm">
|
||||
<li :for={item <- @items} data-testid="server-order-item">{item.name}</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
|
@ -28,6 +28,7 @@ defmodule ExampleWeb.Router do
|
|||
get("/plus-minus-svelte", PageController, :plus_minus_svelte)
|
||||
live("/live-plus-minus", LivePlusMinus)
|
||||
live("/live-plus-minus-hybrid", LivePlusMinusHybrid)
|
||||
live("/live-drag-drop", LiveDragDrop)
|
||||
live("/live-static-color", LiveStaticColor)
|
||||
live("/live-log-list", LiveLogList)
|
||||
live("/live-breaking-news", LiveBreakingNews)
|
||||
|
|
|
|||
37
example_project/package-lock.json
generated
37
example_project/package-lock.json
generated
|
|
@ -5,10 +5,12 @@
|
|||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"dynamic-marquee": "^2.6.5",
|
||||
"live_svelte": "file:../",
|
||||
"phoenix": "file:./deps/phoenix",
|
||||
"phoenix_html": "file:./deps/phoenix_html",
|
||||
"phoenix_live_view": "file:./deps/phoenix_live_view",
|
||||
"svelte-dnd-action": "^0.9.56",
|
||||
"topbar": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -5004,7 +5006,6 @@
|
|||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
|
|
@ -5015,7 +5016,6 @@
|
|||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
|
|
@ -5026,7 +5026,6 @@
|
|||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
|
|
@ -5036,14 +5035,12 @@
|
|||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
|
|
@ -5530,7 +5527,6 @@
|
|||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz",
|
||||
"integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"acorn": "^8.9.0"
|
||||
|
|
@ -5935,7 +5931,6 @@
|
|||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/extend": {
|
||||
|
|
@ -6077,7 +6072,6 @@
|
|||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
|
|
@ -6678,7 +6672,6 @@
|
|||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
|
|
@ -6845,7 +6838,6 @@
|
|||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
|
||||
"integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
@ -6862,7 +6854,6 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
@ -7394,7 +7385,6 @@
|
|||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
|
|
@ -7813,7 +7803,6 @@
|
|||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz",
|
||||
"integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/diff": {
|
||||
|
|
@ -8004,6 +7993,12 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/dynamic-marquee": {
|
||||
"version": "2.6.5",
|
||||
"resolved": "https://registry.npmjs.org/dynamic-marquee/-/dynamic-marquee-2.6.5.tgz",
|
||||
"integrity": "sha512-IfnOHhheSXGjTkLrrfv01x9Ws4O4mLIdzJxP8KTtINeO8hbK0OUV0iDv4qT1QPJRD+JawvrZvjvO7DuNZcO/dQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
|
|
@ -8547,7 +8542,6 @@
|
|||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/espree": {
|
||||
|
|
@ -8599,7 +8593,6 @@
|
|||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz",
|
||||
"integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
|
|
@ -9766,7 +9759,6 @@
|
|||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.6"
|
||||
|
|
@ -13133,7 +13125,6 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
|
|
@ -13205,7 +13196,6 @@
|
|||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
|
|
@ -16051,7 +16041,6 @@
|
|||
"version": "5.53.7",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.7.tgz",
|
||||
"integrity": "sha512-uxck1KI7JWtlfP3H6HOWi/94soAl23jsGJkBzN2BAWcQng0+lTrRNhxActFqORgnO9BHVd1hKJhG+ljRuIUWfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
|
@ -16076,6 +16065,15 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-dnd-action": {
|
||||
"version": "0.9.69",
|
||||
"resolved": "https://registry.npmjs.org/svelte-dnd-action/-/svelte-dnd-action-0.9.69.tgz",
|
||||
"integrity": "sha512-NAmSOH7htJoYraTQvr+q5whlIuVoq88vEuHr4NcFgscDRUxfWPPxgie2OoxepBCQCikrXZV4pqV86aun60wVyw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"svelte": ">=3.23.0 || ^5.0.0-next.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
|
|
@ -17287,7 +17285,6 @@
|
|||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
||||
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/zwitch": {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"dynamic-marquee": "^2.6.5",
|
||||
"live_svelte": "file:../",
|
||||
"phoenix": "file:./deps/phoenix",
|
||||
"phoenix_html": "file:./deps/phoenix_html",
|
||||
"phoenix_live_view": "file:./deps/phoenix_live_view",
|
||||
"svelte-dnd-action": "^0.9.56",
|
||||
"topbar": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
{
|
||||
"css/app.css": {
|
||||
"file": "assets/app-DqfLVfXj.css",
|
||||
"file": "assets/app-HULN4QXK.css",
|
||||
"src": "css/app.css",
|
||||
"isEntry": true,
|
||||
"name": "app",
|
||||
"names": [
|
||||
"app.css"
|
||||
]
|
||||
"isEntry": true
|
||||
},
|
||||
"js/app.js": {
|
||||
"file": "assets/app-BVVDu27N.js",
|
||||
"file": "assets/app-B6UNF5gU.js",
|
||||
"name": "app",
|
||||
"src": "js/app.js",
|
||||
"isEntry": true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
defmodule ExampleWeb.LiveDragDropTest do
|
||||
use ExampleWeb.FeatureCase, async: false
|
||||
|
||||
@moduledoc """
|
||||
E2E tests for the /live-drag-drop LiveView with the DragDrop Svelte component.
|
||||
Validates the full stack: LiveView → LiveSvelte hook → Svelte component renders items.
|
||||
"""
|
||||
@moduletag :e2e
|
||||
|
||||
@item_names [
|
||||
"Design mockups",
|
||||
"Set up database",
|
||||
"Write API endpoints",
|
||||
"Build frontend",
|
||||
"Write tests",
|
||||
"Deploy to production"
|
||||
]
|
||||
|
||||
test "page loads and shows heading and description", %{session: session} do
|
||||
session = visit(session, "/live-drag-drop")
|
||||
|
||||
session |> assert_has(Query.css("h1", text: "Drag & Drop Demo"))
|
||||
|
||||
session
|
||||
|> assert_has(
|
||||
Query.css("p",
|
||||
text:
|
||||
"Reorder tasks with drag and drop. The new order is synced to the server via pushEvent."
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
test "DragDrop Svelte component mounts and renders all 6 task items", %{session: session} do
|
||||
session = visit(session, "/live-drag-drop")
|
||||
|
||||
# Wait for Svelte to mount and render the drag items
|
||||
session = wait_for_drag_items(session, 6)
|
||||
|
||||
items = session |> all(Query.css("[data-testid='drag-item-name']"))
|
||||
assert length(items) == 6
|
||||
|
||||
names = Enum.map(items, &Wallaby.Element.text/1)
|
||||
assert Enum.sort(names) == Enum.sort(@item_names)
|
||||
end
|
||||
|
||||
test "server order list renders all initial items in correct order", %{session: session} do
|
||||
session = visit(session, "/live-drag-drop")
|
||||
|
||||
session |> assert_has(Query.css("[data-testid='server-order-item']", count: 6))
|
||||
|
||||
items = session |> all(Query.css("[data-testid='server-order-item']"))
|
||||
names = Enum.map(items, &Wallaby.Element.text/1)
|
||||
|
||||
assert names == @item_names
|
||||
end
|
||||
|
||||
test "all task names are rendered by Svelte component", %{session: session} do
|
||||
session = visit(session, "/live-drag-drop")
|
||||
session = wait_for_drag_items(session, 6)
|
||||
|
||||
Enum.each(@item_names, fn name ->
|
||||
session |> assert_has(Query.css("[data-testid='drag-item-name']", text: name))
|
||||
end)
|
||||
end
|
||||
|
||||
defp wait_for_drag_items(session, expected, attempts \\ 80) do
|
||||
count = session |> all(Query.css("[data-testid='drag-item-name']")) |> length()
|
||||
|
||||
cond do
|
||||
count >= expected ->
|
||||
session
|
||||
|
||||
attempts == 0 ->
|
||||
raise "timeout waiting for #{expected} drag items, got #{count}"
|
||||
|
||||
true ->
|
||||
:timer.sleep(100)
|
||||
wait_for_drag_items(session, expected, attempts - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
defmodule ExampleWeb.PhoenixTest.LiveDragDropTest do
|
||||
@moduledoc """
|
||||
PhoenixTest (in-process) for LiveDragDrop (/live-drag-drop).
|
||||
Validates that the page renders, the DragDrop Svelte component receives the
|
||||
initial items as props, and that the reorder event updates the server-side order.
|
||||
"""
|
||||
use ExampleWeb.ConnCase, async: false
|
||||
import PhoenixTest
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
@moduletag :phoenix_test
|
||||
|
||||
@initial_items [
|
||||
"Design mockups",
|
||||
"Set up database",
|
||||
"Write API endpoints",
|
||||
"Build frontend",
|
||||
"Write tests",
|
||||
"Deploy to production"
|
||||
]
|
||||
|
||||
test "renders page heading and description", %{conn: conn} do
|
||||
conn
|
||||
|> visit("/live-drag-drop")
|
||||
|> assert_has("h1", text: "Drag & Drop Demo")
|
||||
|> assert_has("p",
|
||||
text: "Reorder tasks with drag and drop. The new order is synced to the server via pushEvent."
|
||||
)
|
||||
end
|
||||
|
||||
test "renders DragDrop Svelte component with initial items in props", %{conn: conn} do
|
||||
conn
|
||||
|> visit("/live-drag-drop")
|
||||
|> assert_has("[data-name='DragDrop']", count: 1)
|
||||
|> assert_has("[data-props*='\"id\":1']")
|
||||
|> assert_has("[data-props*='Design mockups']")
|
||||
end
|
||||
|
||||
test "server order list renders all initial items", %{conn: conn} do
|
||||
conn
|
||||
|> visit("/live-drag-drop")
|
||||
|> assert_has("[data-testid='server-order-item']", count: 6)
|
||||
|> assert_has("[data-testid='server-order-list'] li:first-child",
|
||||
text: "Design mockups"
|
||||
)
|
||||
end
|
||||
|
||||
test "all initial task names appear in the server order list", %{conn: conn} do
|
||||
session = conn |> visit("/live-drag-drop")
|
||||
|
||||
Enum.each(@initial_items, fn name ->
|
||||
assert_has(session, "[data-testid='server-order-item']", text: name)
|
||||
end)
|
||||
end
|
||||
|
||||
test "reorder event updates server-side item order", %{conn: conn} do
|
||||
conn
|
||||
|> visit("/live-drag-drop")
|
||||
|> assert_has("[data-testid='server-order-list'] li:first-child", text: "Design mockups")
|
||||
|> unwrap(fn view ->
|
||||
render_click(view, "reorder", %{"ids" => [2, 1, 3, 4, 5, 6]})
|
||||
end)
|
||||
|> assert_has("[data-testid='server-order-item']", count: 6)
|
||||
|> assert_has("[data-testid='server-order-list'] li:first-child", text: "Set up database")
|
||||
end
|
||||
|
||||
test "reorder event updates data-props to reflect new order", %{conn: conn} do
|
||||
conn
|
||||
|> visit("/live-drag-drop")
|
||||
|> assert_has("[data-props*='\"id\":1']")
|
||||
|> unwrap(fn view ->
|
||||
render_click(view, "reorder", %{"ids" => [2, 1, 3, 4, 5, 6]})
|
||||
end)
|
||||
|> assert_has("[data-name='DragDrop']", count: 1)
|
||||
|> assert_has("[data-props*='\"id\":2']")
|
||||
end
|
||||
|
||||
test "multiple reorders keep all 6 items in server order list", %{conn: conn} do
|
||||
conn
|
||||
|> visit("/live-drag-drop")
|
||||
|> assert_has("[data-testid='server-order-item']", count: 6)
|
||||
|> unwrap(fn view ->
|
||||
render_click(view, "reorder", %{"ids" => [6, 5, 4, 3, 2, 1]})
|
||||
end)
|
||||
|> assert_has("[data-testid='server-order-item']", count: 6)
|
||||
|> assert_has("[data-testid='server-order-list'] li:first-child",
|
||||
text: "Deploy to production"
|
||||
)
|
||||
|> unwrap(fn view ->
|
||||
render_click(view, "reorder", %{"ids" => [1, 2, 3, 4, 5, 6]})
|
||||
end)
|
||||
|> assert_has("[data-testid='server-order-item']", count: 6)
|
||||
|> assert_has("[data-testid='server-order-list'] li:first-child", text: "Design mockups")
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue