Skip to content

Commit b084787

Browse files
committed
Add tests around handling rapid typing.
1 parent 783f85e commit b084787

File tree

5 files changed

+156
-4
lines changed

5 files changed

+156
-4
lines changed

django_unicorn/static/js/component.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
addDbEventListener,
88
addModelEventListener,
99
} from "./eventListeners.js";
10+
import morphdom from "./morphdom/2.6.1/morphdom.js";
1011

1112
/**
1213
* Encapsulate component.
@@ -30,6 +31,7 @@ export class Component {
3031
this.document = args.document || document;
3132
this.walker = args.walker || walk;
3233
this.window = args.window || window;
34+
this.morphdom = args.morphdom || morphdom;
3335

3436
this.root = undefined;
3537
this.modelEls = [];
@@ -353,9 +355,9 @@ export class Component {
353355

354356
this.modelEls.forEach((element) => {
355357
if (
358+
forceModelUpdates ||
356359
!lastTriggeringElement ||
357-
!lastTriggeringElement.isSame(element) ||
358-
forceModelUpdates
360+
!lastTriggeringElement.isSame(element)
359361
) {
360362
this.setValue(element);
361363
}

django_unicorn/static/js/messageSender.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { getCsrfToken, hasValue, isFunction } from "./utils.js";
2-
import morphdom from "./morphdom/2.6.1/morphdom.js";
32
import { MORPHDOM_OPTIONS } from "./morphdom/2.6.1/options.js";
43

54
/**
@@ -117,7 +116,7 @@ export function send(component, callback) {
117116
component.startPolling();
118117
}
119118

120-
morphdom(component.root, rerenderedComponent, MORPHDOM_OPTIONS);
119+
component.morphdom(component.root, rerenderedComponent, MORPHDOM_OPTIONS);
121120

122121
// Refresh the checksum based on the new data
123122
component.refreshChecksum();

tests/js/component/messageSender.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,42 @@ test.cb("call_method hash", (t) => {
126126
t.end();
127127
});
128128
});
129+
130+
test.cb("call_method forceModelUpdate is true", (t) => {
131+
const html = `
132+
<input type="hidden" name="csrfmiddlewaretoken" value="asdf">
133+
<div unicorn:id="5jypjiyb" unicorn:name="text-inputs" unicorn:checksum="GXzew3Km">
134+
<input unicorn:model='name'></input>
135+
<button unicorn:click='test()'><span id="clicker">Click</span></button>
136+
</div>
137+
`;
138+
139+
const component = getComponent(html);
140+
141+
t.is(component.attachedEventTypes.length, 1);
142+
t.is(component.actionEvents.click.length, 1);
143+
144+
component.actionEvents.click[0].element.el.click();
145+
146+
t.is(component.actionQueue.length, 1);
147+
148+
// mock the fetch
149+
const res = {
150+
id: "aQzrrRoG",
151+
dom: "blob",
152+
data: {
153+
name: "World",
154+
},
155+
errors: {},
156+
redirect: {},
157+
return: {},
158+
};
159+
global.fetch = fetchMock.sandbox().mock().post("/test/text-inputs", res);
160+
161+
send(component, (a, forceModelUpdates, err) => {
162+
t.not(err);
163+
t.true(forceModelUpdates);
164+
fetchMock.reset();
165+
t.end();
166+
});
167+
});
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import test from "ava";
2+
import { getComponent } from "../utils.js";
3+
4+
test("setModelValues setValue called", (t) => {
5+
t.plan(4);
6+
7+
const html = `
8+
<div unicorn:id="5jypjiyb" unicorn:name="text-inputs" unicorn:checksum="GXzew3Km">
9+
<input u:model='name'></input>
10+
</div>`;
11+
const component = getComponent(html);
12+
13+
t.is(component.modelEls.length, 1);
14+
15+
let setValueCalled = false;
16+
let elementSet = null;
17+
18+
component.setValue = (element) => {
19+
t.pass();
20+
setValueCalled = true;
21+
elementSet = element;
22+
};
23+
24+
const triggeringElements = [];
25+
const forceModelUpdates = false;
26+
component.setModelValues(triggeringElements, forceModelUpdates);
27+
28+
t.true(setValueCalled);
29+
t.is(elementSet, component.modelEls[0]);
30+
});
31+
32+
test("setModelValues triggeringElement setValue not called", (t) => {
33+
t.plan(3);
34+
35+
const html = `
36+
<div unicorn:id="5jypjiyb" unicorn:name="text-inputs" unicorn:checksum="GXzew3Km">
37+
<input u:model='name'></input>
38+
</div>`;
39+
const component = getComponent(html);
40+
41+
t.is(component.modelEls.length, 1);
42+
43+
let setValueCalled = false;
44+
let elementSet = null;
45+
46+
component.setValue = (element) => {
47+
t.pass();
48+
setValueCalled = true;
49+
elementSet = element;
50+
};
51+
52+
const triggeringElements = [component.modelEls[0]];
53+
const forceModelUpdates = false;
54+
component.setModelValues(triggeringElements, forceModelUpdates);
55+
56+
t.false(setValueCalled);
57+
t.is(elementSet, null);
58+
});
59+
60+
test("setModelValues triggeringElement forceModelUpdates setValue called", (t) => {
61+
t.plan(4);
62+
63+
const html = `
64+
<div unicorn:id="5jypjiyb" unicorn:name="text-inputs" unicorn:checksum="GXzew3Km">
65+
<input u:model='name'></input>
66+
</div>`;
67+
const component = getComponent(html);
68+
69+
t.is(component.modelEls.length, 1);
70+
71+
let setValueCalled = false;
72+
let elementSet = null;
73+
74+
component.setValue = (element) => {
75+
t.pass();
76+
setValueCalled = true;
77+
elementSet = element;
78+
};
79+
80+
const triggeringElements = [component.modelEls[0]];
81+
const forceModelUpdates = true;
82+
component.setModelValues(triggeringElements, forceModelUpdates);
83+
84+
t.true(setValueCalled);
85+
t.is(elementSet, component.modelEls[0]);
86+
});
87+
88+
test("setModelValues multiple models setValue called once", (t) => {
89+
t.plan(2);
90+
91+
const html = `
92+
<div unicorn:id="5jypjiyb" unicorn:name="text-inputs" unicorn:checksum="GXzew3Km">
93+
<input u:model='name'></input>
94+
<input u:model='blob'></input>
95+
</div>`;
96+
const component = getComponent(html);
97+
98+
t.is(component.modelEls.length, 2);
99+
100+
component.setValue = (_) => {
101+
t.pass();
102+
};
103+
104+
const triggeringElements = [component.modelEls[0]];
105+
const forceModelUpdates = false;
106+
component.setModelValues(triggeringElements, forceModelUpdates);
107+
});

tests/js/utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ export function walkDOM(el, callback) {
5656
} while ((el = el.nextSibling));
5757
}
5858

59+
function morphdom(initial, merge, options) {
60+
return initial;
61+
}
62+
5963
/**
6064
* Gets a constructed `Component` based on the passed-in HTML fragement.
6165
* @param {String} html THe HTML fragment for the component.
@@ -103,6 +107,7 @@ export function getComponent(html, id, name, data) {
103107
document,
104108
messageUrl: "test",
105109
walker: walkDOM,
110+
morphdom,
106111
window: {
107112
document: { title: "" },
108113
history: mockHistory,

0 commit comments

Comments
 (0)