Skip to content

Commit 32ccb8f

Browse files
JohananOppongAmoatengadamghill
authored andcommitted
loading delay modifier
1 parent 8fb29ee commit 32ccb8f

File tree

3 files changed

+108
-0
lines changed

3 files changed

+108
-0
lines changed

src/django_unicorn/static/unicorn/js/element.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ export class Element {
8888
} else if (attribute.isLoading) {
8989
this.loading.show = true;
9090
}
91+
92+
if (this.loading) {
93+
if (attribute.modifiers.delay) {
94+
const delayValue = parseInt(attribute.modifiers.delay, 10);
95+
this.loading.delay = Number.isNaN(delayValue) ? 200 : delayValue;
96+
}
97+
}
9198
} else if (attribute.isTarget) {
9299
this.target = attribute.value;
93100
} else if (attribute.isPartial) {
@@ -201,6 +208,22 @@ export class Element {
201208
* @param {bool} revert Whether or not the revert the loading class.
202209
*/
203210
handleLoading(revert) {
211+
if (this.loading.delay) {
212+
if (revert) {
213+
if (this.loading.timer) {
214+
clearTimeout(this.loading.timer);
215+
this.loading.timer = null;
216+
return;
217+
}
218+
} else {
219+
this.loading.timer = setTimeout(() => {
220+
this.handleInterfacer("loading", revert);
221+
this.loading.timer = null;
222+
}, this.loading.delay);
223+
return;
224+
}
225+
}
226+
204227
this.handleInterfacer("loading", revert);
205228
}
206229

tests/js/element/loading.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,18 @@ test("loading attr", (t) => {
4444

4545
t.is(element.loading.attr, "disabled");
4646
});
47+
48+
test("loading delay", (t) => {
49+
const html = "<div u:click='update()' u:loading.delay></div>";
50+
const element = getElement(html);
51+
52+
t.is(element.loading.delay, 200);
53+
});
54+
55+
test("loading delay with time", (t) => {
56+
const html = "<div u:click='update()' u:loading.delay-1000></div>";
57+
const element = getElement(html);
58+
59+
t.is(element.loading.delay, 1000);
60+
});
61+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import test from "ava";
2+
import fetchMock from "fetch-mock";
3+
import { send } from "../../../src/django_unicorn/static/unicorn/js/messageSender.js";
4+
import { getComponent } from "../utils.js";
5+
6+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
7+
8+
test("loading delay shows after timeout", async (t) => {
9+
const html = `
10+
<div unicorn:id="5jypjiyb" unicorn:name="text-inputs" unicorn:checksum="GXzew3Km">
11+
<button unicorn:click='test()' u:loading.class="loading" u:loading.delay-100></button>
12+
</div>`;
13+
const component = getComponent(html);
14+
const { el } = component.actionEvents.click[0].element;
15+
16+
// Initial state: not loading
17+
t.is(el.classList.length, 0);
18+
19+
// Trigger click
20+
el.click();
21+
22+
// Immediately after click: still not loading (because of delay)
23+
t.is(el.classList.length, 0);
24+
25+
// Wait 50ms (less than 100ms): still not loading
26+
await delay(50);
27+
t.is(el.classList.length, 0);
28+
29+
// Wait 100ms more (total 150ms): should be loading
30+
await delay(100);
31+
t.is(el.classList.length, 1);
32+
t.is(el.classList[0], "loading");
33+
});
34+
35+
test("loading delay does not show if action takes less time", async (t) => {
36+
const html = `
37+
<div unicorn:id="5jypjiyb" unicorn:name="text-inputs" unicorn:checksum="GXzew3Km">
38+
<button unicorn:click='test()' u:loading.class="loading" u:loading.delay-200></button>
39+
</div>`;
40+
const component = getComponent(html);
41+
const { el } = component.actionEvents.click[0].element;
42+
43+
// Trigger click
44+
el.click();
45+
46+
// Immediately after click: not loading
47+
t.is(el.classList.length, 0);
48+
49+
// Mock fetch to return quickly (e.g. 50ms)
50+
// We mock a delay in the mock response using delay() inside the response if possible,
51+
// or just use fetchMock delay option.
52+
global.fetch = fetchMock.sandbox().mock().post("/test/text-inputs", {
53+
body: {},
54+
delay: 50, // 50ms delay
55+
});
56+
57+
// Execute send (simulating the action completing)
58+
// send returns a promise that resolves when the action is done
59+
const sendPromise = send(component);
60+
61+
// Wait 100ms (total from start > 50ms response time but < 200ms loading delay)
62+
await delay(100);
63+
64+
// loading should NOT have shown because action finished at 50ms
65+
// Check if class is present
66+
t.is(el.classList.length, 0);
67+
68+
await sendPromise;
69+
fetchMock.reset();
70+
});

0 commit comments

Comments
 (0)