mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-09-28 13:31:06 +00:00
0.1.1 release - fix double event dispatch bug
This commit is contained in:
parent
c218853011
commit
4e15653adb
7
dist/htmx.js
vendored
7
dist/htmx.js
vendored
@ -1139,8 +1139,9 @@ return (function () {
|
||||
triggerEvent(elt, "htmx:error", {errorInfo:detail})
|
||||
}
|
||||
var eventResult = elt.dispatchEvent(event);
|
||||
if (eventResult) {
|
||||
var kebabedEvent = makeEvent(kebabEventName(eventName), event.detail);
|
||||
var kebabName = kebabEventName(eventName);
|
||||
if (eventResult && kebabName !== eventName) {
|
||||
var kebabedEvent = makeEvent(kebabName, event.detail);
|
||||
eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
|
||||
}
|
||||
withExtensions(elt, function (extension) {
|
||||
@ -1653,7 +1654,7 @@ return (function () {
|
||||
try {
|
||||
if (!triggerEvent(elt, 'htmx:beforeOnLoad', eventDetail)) return;
|
||||
|
||||
//AK - FIXED CHROME issue - "refuse to get unsafe header" warning.
|
||||
//AK - FIXED CHROME issue - "refuse to get unsafe header" warning.
|
||||
if (xhr.getAllResponseHeaders().indexOf("HX-Trigger") >= 0) {
|
||||
handleTrigger(elt, this.getResponseHeader("HX-Trigger"));
|
||||
}
|
||||
|
2
dist/htmx.min.js
vendored
2
dist/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
BIN
dist/htmx.min.js.gz
vendored
BIN
dist/htmx.min.js.gz
vendored
Binary file not shown.
@ -5,7 +5,7 @@
|
||||
"AJAX",
|
||||
"HTML"
|
||||
],
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"homepage": "https://htmx.org/",
|
||||
"bugs": {
|
||||
"url": "https://github.com/bigskysoftware/htmx/issues"
|
||||
|
@ -1139,8 +1139,9 @@ return (function () {
|
||||
triggerEvent(elt, "htmx:error", {errorInfo:detail})
|
||||
}
|
||||
var eventResult = elt.dispatchEvent(event);
|
||||
if (eventResult) {
|
||||
var kebabedEvent = makeEvent(kebabEventName(eventName), event.detail);
|
||||
var kebabName = kebabEventName(eventName);
|
||||
if (eventResult && kebabName !== eventName) {
|
||||
var kebabedEvent = makeEvent(kebabName, event.detail);
|
||||
eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
|
||||
}
|
||||
withExtensions(elt, function (extension) {
|
||||
|
@ -63,6 +63,20 @@ describe("Core htmx Events", function() {
|
||||
}
|
||||
});
|
||||
|
||||
it("events are only dispatched once if kebab and camel case match", function () {
|
||||
var invoked = 0;
|
||||
var handler = htmx.on("custom", function () {
|
||||
invoked = invoked + 1
|
||||
});
|
||||
try {
|
||||
var div = make("<div hx-post='/test'></div>");
|
||||
htmx.trigger(div, "custom");
|
||||
invoked.should.equal(1);
|
||||
} finally {
|
||||
htmx.off("custom", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:configRequest allows attribute removal", function () {
|
||||
var param = "foo";
|
||||
var handler = htmx.on("htmx:configRequest", function (evt) {
|
||||
|
@ -1139,8 +1139,9 @@ return (function () {
|
||||
triggerEvent(elt, "htmx:error", {errorInfo:detail})
|
||||
}
|
||||
var eventResult = elt.dispatchEvent(event);
|
||||
if (eventResult) {
|
||||
var kebabedEvent = makeEvent(kebabEventName(eventName), event.detail);
|
||||
var kebabName = kebabEventName(eventName);
|
||||
if (eventResult && kebabName !== eventName) {
|
||||
var kebabedEvent = makeEvent(kebabName, event.detail);
|
||||
eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
|
||||
}
|
||||
withExtensions(elt, function (extension) {
|
||||
@ -1653,8 +1654,15 @@ return (function () {
|
||||
try {
|
||||
if (!triggerEvent(elt, 'htmx:beforeOnLoad', eventDetail)) return;
|
||||
|
||||
handleTrigger(elt, this.getResponseHeader("HX-Trigger"));
|
||||
var pushedUrl = this.getResponseHeader("HX-Push");
|
||||
//AK - FIXED CHROME issue - "refuse to get unsafe header" warning.
|
||||
if (xhr.getAllResponseHeaders().indexOf("HX-Trigger") >= 0) {
|
||||
handleTrigger(elt, this.getResponseHeader("HX-Trigger"));
|
||||
}
|
||||
|
||||
if (xhr.getAllResponseHeaders().indexOf("HX-Push") >= 0) {
|
||||
var pushedUrl = this.getResponseHeader("HX-Push");
|
||||
}
|
||||
//AK - FIXED CHROME issue - "refuse to get unsafe header" warning. \\
|
||||
|
||||
var shouldSaveHistory = shouldPush(elt) || pushedUrl;
|
||||
|
||||
|
10854
www/test/0.1.1/node_modules/chai/chai.js
generated
vendored
Normal file
10854
www/test/0.1.1/node_modules/chai/chai.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
325
www/test/0.1.1/node_modules/mocha/mocha.css
generated
vendored
Normal file
325
www/test/0.1.1/node_modules/mocha/mocha.css
generated
vendored
Normal file
@ -0,0 +1,325 @@
|
||||
@charset "utf-8";
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
}
|
||||
|
||||
#mocha {
|
||||
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
margin: 60px 50px;
|
||||
}
|
||||
|
||||
#mocha ul,
|
||||
#mocha li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#mocha ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#mocha h1,
|
||||
#mocha h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#mocha h1 {
|
||||
margin-top: 15px;
|
||||
font-size: 1em;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
#mocha h1 a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#mocha h1 a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#mocha .suite .suite h1 {
|
||||
margin-top: 0;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
#mocha .hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#mocha .suite {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
#mocha .test {
|
||||
margin-left: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#mocha .test.pending:hover h2::after {
|
||||
content: '(pending)';
|
||||
font-family: arial, sans-serif;
|
||||
}
|
||||
|
||||
#mocha .test.pass.medium .duration {
|
||||
background: #c09853;
|
||||
}
|
||||
|
||||
#mocha .test.pass.slow .duration {
|
||||
background: #b94a48;
|
||||
}
|
||||
|
||||
#mocha .test.pass::before {
|
||||
content: '✓';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #00d6b2;
|
||||
}
|
||||
|
||||
#mocha .test.pass .duration {
|
||||
font-size: 9px;
|
||||
margin-left: 5px;
|
||||
padding: 2px 5px;
|
||||
color: #fff;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-ms-border-radius: 5px;
|
||||
-o-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#mocha .test.pass.fast .duration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha .test.pending {
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.pending::before {
|
||||
content: '◦';
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.fail {
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test.fail pre {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#mocha .test.fail::before {
|
||||
content: '✖';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test pre.error {
|
||||
color: #c00;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#mocha .test .html-error {
|
||||
overflow: auto;
|
||||
color: black;
|
||||
display: block;
|
||||
float: left;
|
||||
clear: left;
|
||||
font: 12px/1.5 monaco, monospace;
|
||||
margin: 5px;
|
||||
padding: 15px;
|
||||
border: 1px solid #eee;
|
||||
max-width: 85%; /*(1)*/
|
||||
max-width: -webkit-calc(100% - 42px);
|
||||
max-width: -moz-calc(100% - 42px);
|
||||
max-width: calc(100% - 42px); /*(2)*/
|
||||
max-height: 300px;
|
||||
word-wrap: break-word;
|
||||
border-bottom-color: #ddd;
|
||||
-webkit-box-shadow: 0 1px 3px #eee;
|
||||
-moz-box-shadow: 0 1px 3px #eee;
|
||||
box-shadow: 0 1px 3px #eee;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#mocha .test .html-error pre.error {
|
||||
border: none;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: 0;
|
||||
-moz-box-shadow: 0;
|
||||
box-shadow: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: 18px;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* (1): approximate for browsers not supporting calc
|
||||
* (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
|
||||
* ^^ seriously
|
||||
*/
|
||||
#mocha .test pre {
|
||||
display: block;
|
||||
float: left;
|
||||
clear: left;
|
||||
font: 12px/1.5 monaco, monospace;
|
||||
margin: 5px;
|
||||
padding: 15px;
|
||||
border: 1px solid #eee;
|
||||
max-width: 85%; /*(1)*/
|
||||
max-width: -webkit-calc(100% - 42px);
|
||||
max-width: -moz-calc(100% - 42px);
|
||||
max-width: calc(100% - 42px); /*(2)*/
|
||||
word-wrap: break-word;
|
||||
border-bottom-color: #ddd;
|
||||
-webkit-box-shadow: 0 1px 3px #eee;
|
||||
-moz-box-shadow: 0 1px 3px #eee;
|
||||
box-shadow: 0 1px 3px #eee;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#mocha .test h2 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#mocha .test a.replay {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 0;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
background: #eee;
|
||||
font-size: 15px;
|
||||
-webkit-border-radius: 15px;
|
||||
-moz-border-radius: 15px;
|
||||
border-radius: 15px;
|
||||
-webkit-transition:opacity 200ms;
|
||||
-moz-transition:opacity 200ms;
|
||||
-o-transition:opacity 200ms;
|
||||
transition: opacity 200ms;
|
||||
opacity: 0.3;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#mocha .test:hover a.replay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#mocha-report.pass .test.fail {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha-report.fail .test.pass {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha-report.pending .test.pass,
|
||||
#mocha-report.pending .test.fail {
|
||||
display: none;
|
||||
}
|
||||
#mocha-report.pending .test.pass.pending {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#mocha-error {
|
||||
color: #c00;
|
||||
font-size: 1.5em;
|
||||
font-weight: 100;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
#mocha-stats {
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
right: 10px;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
color: #888;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#mocha-stats .progress {
|
||||
float: right;
|
||||
padding-top: 0;
|
||||
|
||||
/**
|
||||
* Set safe initial values, so mochas .progress does not inherit these
|
||||
* properties from Bootstrap .progress (which causes .progress height to
|
||||
* equal line height set in Bootstrap).
|
||||
*/
|
||||
height: auto;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
background-color: initial;
|
||||
}
|
||||
|
||||
#mocha-stats em {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#mocha-stats a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#mocha-stats a:hover {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#mocha-stats li {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
list-style: none;
|
||||
padding-top: 11px;
|
||||
}
|
||||
|
||||
#mocha-stats canvas {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#mocha code .comment { color: #ddd; }
|
||||
#mocha code .init { color: #2f6fad; }
|
||||
#mocha code .string { color: #5890ad; }
|
||||
#mocha code .keyword { color: #8a6343; }
|
||||
#mocha code .number { color: #2f6fad; }
|
||||
|
||||
@media screen and (max-device-width: 480px) {
|
||||
#mocha {
|
||||
margin: 60px 0px;
|
||||
}
|
||||
|
||||
#mocha #stats {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
18178
www/test/0.1.1/node_modules/mocha/mocha.js
generated
vendored
Normal file
18178
www/test/0.1.1/node_modules/mocha/mocha.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
16430
www/test/0.1.1/node_modules/sinon/pkg/sinon.js
generated
vendored
Normal file
16430
www/test/0.1.1/node_modules/sinon/pkg/sinon.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
7
www/test/0.1.1/src/ext/ajax-header.js
Normal file
7
www/test/0.1.1/src/ext/ajax-header.js
Normal file
@ -0,0 +1,7 @@
|
||||
htmx.defineExtension('ajax-header', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "htmx:configRequest") {
|
||||
evt.detail.headers['X-Requested-With'] = 'XMLHttpRequest';
|
||||
}
|
||||
}
|
||||
});
|
84
www/test/0.1.1/src/ext/class-tools.js
Normal file
84
www/test/0.1.1/src/ext/class-tools.js
Normal file
@ -0,0 +1,84 @@
|
||||
(function(){
|
||||
|
||||
function splitOnWhitespace(trigger) {
|
||||
return trigger.split(/\s+/);
|
||||
}
|
||||
|
||||
function parseClassOperation(trimmedValue) {
|
||||
var split = splitOnWhitespace(trimmedValue);
|
||||
if (split.length > 1) {
|
||||
var operation = split[0];
|
||||
var classDef = split[1].trim();
|
||||
var cssClass;
|
||||
var delay;
|
||||
if (classDef.indexOf(":") > 0) {
|
||||
var splitCssClass = classDef.split(':');
|
||||
cssClass = splitCssClass[0];
|
||||
delay = htmx.parseInterval(splitCssClass[1]);
|
||||
} else {
|
||||
cssClass = classDef;
|
||||
delay = 100;
|
||||
}
|
||||
return {
|
||||
operation:operation,
|
||||
cssClass:cssClass,
|
||||
delay:delay
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function processClassList(elt, classList) {
|
||||
var runs = classList.split("&");
|
||||
for (var i = 0; i < runs.length; i++) {
|
||||
var run = runs[i];
|
||||
var currentRunTime = 0;
|
||||
var classOperations = run.split(",");
|
||||
for (var j = 0; j < classOperations.length; j++) {
|
||||
var value = classOperations[j];
|
||||
var trimmedValue = value.trim();
|
||||
var classOperation = parseClassOperation(trimmedValue);
|
||||
if (classOperation) {
|
||||
if (classOperation.operation === "toggle") {
|
||||
setTimeout(function () {
|
||||
setInterval(function () {
|
||||
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
|
||||
}, classOperation.delay);
|
||||
}, currentRunTime);
|
||||
currentRunTime = currentRunTime + classOperation.delay;
|
||||
} else {
|
||||
currentRunTime = currentRunTime + classOperation.delay;
|
||||
setTimeout(function () {
|
||||
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
|
||||
}, currentRunTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function maybeProcessClasses(elt) {
|
||||
if (elt.getAttribute) {
|
||||
var classList = elt.getAttribute("classes") || elt.getAttribute("data-classes");
|
||||
if (classList) {
|
||||
processClassList(elt, classList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
htmx.defineExtension('class-tools', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "htmx:processedNode") {
|
||||
var elt = evt.detail.elt;
|
||||
maybeProcessClasses(elt);
|
||||
if (elt.querySelectorAll) {
|
||||
var children = elt.querySelectorAll("[classes], [data-classes]");
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
maybeProcessClasses(children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
37
www/test/0.1.1/src/ext/client-side-templates.js
Normal file
37
www/test/0.1.1/src/ext/client-side-templates.js
Normal file
@ -0,0 +1,37 @@
|
||||
htmx.defineExtension('client-side-templates', {
|
||||
transformResponse : function(text, xhr, elt) {
|
||||
|
||||
var mustacheTemplate = htmx.closest(elt, "[mustache-template]");
|
||||
if (mustacheTemplate) {
|
||||
var data = JSON.parse(text);
|
||||
var templateId = mustacheTemplate.getAttribute('mustache-template');
|
||||
var template = htmx.find("#" + templateId);
|
||||
if (template) {
|
||||
return Mustache.render(template.innerHTML, data);
|
||||
} else {
|
||||
throw "Unknown mustache template: " + templateId;
|
||||
}
|
||||
}
|
||||
|
||||
var handlebarsTemplate = htmx.closest(elt, "[handlebars-template]");
|
||||
if (handlebarsTemplate) {
|
||||
var data = JSON.parse(text);
|
||||
var templateName = handlebarsTemplate.getAttribute('handlebars-template');
|
||||
return Handlebars.partials[templateName](data);
|
||||
}
|
||||
|
||||
var nunjucksTemplate = htmx.closest(elt, "[nunjucks-template]");
|
||||
if (nunjucksTemplate) {
|
||||
var data = JSON.parse(text);
|
||||
var templateName = nunjucksTemplate.getAttribute('nunjucks-template');
|
||||
var template = htmx.find('#' + templateName);
|
||||
if (template) {
|
||||
return nunjucks.renderString(template.innerHTML, data);
|
||||
} else {
|
||||
return nunjucks.render(templateName, data);
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
});
|
11
www/test/0.1.1/src/ext/debug.js
Normal file
11
www/test/0.1.1/src/ext/debug.js
Normal file
@ -0,0 +1,11 @@
|
||||
htmx.defineExtension('debug', {
|
||||
onEvent: function (name, evt) {
|
||||
if (console.debug) {
|
||||
console.debug(name, evt);
|
||||
} else if (console) {
|
||||
console.log("DEBUG:", name, evt);
|
||||
} else {
|
||||
throw "NO CONSOLE SUPPORTED"
|
||||
}
|
||||
}
|
||||
});
|
24
www/test/0.1.1/src/ext/include-vals.js
Normal file
24
www/test/0.1.1/src/ext/include-vals.js
Normal file
@ -0,0 +1,24 @@
|
||||
(function(){
|
||||
|
||||
function mergeObjects(obj1, obj2) {
|
||||
for (var key in obj2) {
|
||||
if (obj2.hasOwnProperty(key)) {
|
||||
obj1[key] = obj2[key];
|
||||
}
|
||||
}
|
||||
return obj1;
|
||||
}
|
||||
|
||||
htmx.defineExtension('include-vals', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "htmx:configRequest") {
|
||||
var includeValsElt = htmx.closest(evt.detail.elt, "[include-vals],[data-include-vals]");
|
||||
if (includeValsElt) {
|
||||
var includeVals = includeValsElt.getAttribute("include-vals") || includeValsElt.getAttribute("data-include-vals");
|
||||
var valuesToInclude = eval("({" + includeVals + "})");
|
||||
mergeObjects(evt.detail.parameters, valuesToInclude);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
12
www/test/0.1.1/src/ext/json-enc.js
Normal file
12
www/test/0.1.1/src/ext/json-enc.js
Normal file
@ -0,0 +1,12 @@
|
||||
htmx.defineExtension('json-enc', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "htmx:configRequest") {
|
||||
evt.detail.headers['Content-Type'] = "application/json";
|
||||
}
|
||||
},
|
||||
|
||||
encodeParameters : function(xhr, parameters, elt) {
|
||||
xhr.overrideMimeType('text/json');
|
||||
return (JSON.stringify(parameters));
|
||||
}
|
||||
});
|
11
www/test/0.1.1/src/ext/method-override.js
Normal file
11
www/test/0.1.1/src/ext/method-override.js
Normal file
@ -0,0 +1,11 @@
|
||||
htmx.defineExtension('method-override', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "htmx:configRequest") {
|
||||
var method = evt.detail.verb;
|
||||
if (method !== "get" || method !== "post") {
|
||||
evt.detail.headers['X-HTTP-Method-Override'] = method.toUpperCase();
|
||||
evt.detail.verb = "post";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
11
www/test/0.1.1/src/ext/morphdom-swap.js
Normal file
11
www/test/0.1.1/src/ext/morphdom-swap.js
Normal file
@ -0,0 +1,11 @@
|
||||
htmx.defineExtension('morphdom-swap', {
|
||||
isInlineSwap: function(swapStyle) {
|
||||
return swapStyle === 'morphdom';
|
||||
},
|
||||
handleSwap: function (swapStyle, target, fragment) {
|
||||
if (swapStyle === 'morphdom') {
|
||||
morphdom(target, fragment.outerHTML);
|
||||
return [target]; // let htmx handle the new content
|
||||
}
|
||||
}
|
||||
});
|
35
www/test/0.1.1/src/ext/path-deps.js
Normal file
35
www/test/0.1.1/src/ext/path-deps.js
Normal file
@ -0,0 +1,35 @@
|
||||
(function(){
|
||||
function dependsOn(pathSpec, url) {
|
||||
var dependencyPath = pathSpec.split("/");
|
||||
var urlPath = url.split("/");
|
||||
for (var i = 0; i < urlPath.length; i++) {
|
||||
var dependencyElement = dependencyPath.shift();
|
||||
var pathElement = urlPath[i];
|
||||
if (dependencyElement !== pathElement && dependencyElement !== "*") {
|
||||
return false;
|
||||
}
|
||||
if (dependencyPath.length === 0 || (dependencyPath.length === 1 && dependencyPath[0] === "")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
htmx.defineExtension('path-deps', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "htmx:afterRequest") {
|
||||
var xhr = evt.detail.xhr;
|
||||
// mutating call
|
||||
if (xhr.method !== "GET") {
|
||||
var eltsWithDeps = htmx.findAll("[path-deps]");
|
||||
for (var i = 0; i < eltsWithDeps.length; i++) {
|
||||
var elt = eltsWithDeps[i];
|
||||
if (dependsOn(elt.getAttribute('path-deps'), xhr.url)) {
|
||||
htmx.trigger(elt, "path-deps");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
27
www/test/0.1.1/src/ext/remove-me.js
Normal file
27
www/test/0.1.1/src/ext/remove-me.js
Normal file
@ -0,0 +1,27 @@
|
||||
(function(){
|
||||
function maybeRemoveMe(elt) {
|
||||
var timing = elt.getAttribute("remove-me") || elt.getAttribute("data-remove-me");
|
||||
if (timing) {
|
||||
setTimeout(function () {
|
||||
elt.parentElement.removeChild(elt);
|
||||
}, htmx.parseInterval(timing));
|
||||
}
|
||||
}
|
||||
|
||||
htmx.defineExtension('remove-me', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "htmx:processedNode") {
|
||||
var elt = evt.detail.elt;
|
||||
if (elt.getAttribute) {
|
||||
maybeRemoveMe(elt);
|
||||
if (elt.querySelectorAll) {
|
||||
var children = elt.querySelectorAll("[remove-me], [data-remove-me");
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
maybeRemoveMe(children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
1880
www/test/0.1.1/src/htmx.js
Normal file
1880
www/test/0.1.1/src/htmx.js
Normal file
File diff suppressed because it is too large
Load Diff
61
www/test/0.1.1/test/attributes/hx-boost.js
Normal file
61
www/test/0.1.1/test/attributes/hx-boost.js
Normal file
@ -0,0 +1,61 @@
|
||||
describe("hx-boost attribute", function() {
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('handles basic anchor properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Boosted");
|
||||
var div = make('<div hx-target="this" hx-boost="true"><a id="a1" href="/test">Foo</a></div>');
|
||||
var a = byId('a1');
|
||||
a.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Boosted");
|
||||
})
|
||||
|
||||
|
||||
it('handles basic form post properly', function () {
|
||||
this.server.respondWith("POST", "/test", "Boosted");
|
||||
this.server.respondWith("POST", "/test", "Boosted");
|
||||
var div = make('<div hx-target="this" hx-boost="true"><form id="f1" action="/test" method="post"><button id="b1">Submit</button></form></div>');
|
||||
var btn = byId('b1');
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Boosted");
|
||||
})
|
||||
|
||||
it('handles basic form get properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Boosted");
|
||||
var div = make('<div hx-target="this" hx-boost="true"><form id="f1" action="/test" method="get"><button id="b1">Submit</button></form></div>');
|
||||
var btn = byId('b1');
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Boosted");
|
||||
})
|
||||
|
||||
it('handles basic form with no explicit method property', function () {
|
||||
this.server.respondWith("GET", "/test", "Boosted");
|
||||
var div = make('<div hx-target="this" hx-boost="true"><form id="f1" action="/test"><button id="b1">Submit</button></form></div>');
|
||||
var btn = byId('b1');
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Boosted");
|
||||
})
|
||||
|
||||
it('handles basic anchor properly w/ data-* prefix', function () {
|
||||
this.server.respondWith("GET", "/test", "Boosted");
|
||||
var div = make('<div data-hx-target="this" data-hx-boost="true"><a id="a1" href="/test">Foo</a></div>');
|
||||
var a = byId('a1');
|
||||
a.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Boosted");
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
34
www/test/0.1.1/test/attributes/hx-delete.js
Normal file
34
www/test/0.1.1/test/attributes/hx-delete.js
Normal file
@ -0,0 +1,34 @@
|
||||
describe("hx-delete attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a DELETE request', function()
|
||||
{
|
||||
this.server.respondWith("DELETE", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Deleted!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-delete="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Deleted!");
|
||||
});
|
||||
|
||||
it('issues a DELETE request w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("DELETE", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Deleted!");
|
||||
});
|
||||
|
||||
var btn = make('<button data-hx-delete="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Deleted!");
|
||||
});
|
||||
})
|
98
www/test/0.1.1/test/attributes/hx-ext.js
Normal file
98
www/test/0.1.1/test/attributes/hx-ext.js
Normal file
@ -0,0 +1,98 @@
|
||||
describe("hx-ext attribute", function() {
|
||||
|
||||
var ext1Calls, ext2Calls, ext3Calls;
|
||||
|
||||
beforeEach(function () {
|
||||
ext1Calls = ext2Calls = ext3Calls = 0;
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
htmx.defineExtension("ext-1", {
|
||||
onEvent : function(name, evt) {
|
||||
if(name === "htmx:afterRequest"){
|
||||
ext1Calls++;
|
||||
}
|
||||
}
|
||||
});
|
||||
htmx.defineExtension("ext-2", {
|
||||
onEvent : function(name, evt) {
|
||||
if(name === "htmx:afterRequest"){
|
||||
ext2Calls++;
|
||||
}
|
||||
}
|
||||
});
|
||||
htmx.defineExtension("ext-3", {
|
||||
onEvent : function(name, evt) {
|
||||
if(name === "htmx:afterRequest"){
|
||||
ext3Calls++;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
htmx.removeExtension("ext-1");
|
||||
htmx.removeExtension("ext-2");
|
||||
htmx.removeExtension("ext-3");
|
||||
});
|
||||
|
||||
it('A simple extension is invoked properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button hx-get="/test" hx-ext="ext-1">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
ext1Calls.should.equal(1);
|
||||
ext2Calls.should.equal(0);
|
||||
ext3Calls.should.equal(0);
|
||||
});
|
||||
|
||||
it('Extensions are merged properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
make('<div hx-ext="ext-1"><button id="btn-1" hx-get="/test" hx-ext="ext-2">Click Me!</button>' +
|
||||
'<button id="btn-2" hx-get="/test" hx-ext="ext-3">Click Me!</button></div>')
|
||||
var btn1 = byId("btn-1");
|
||||
var btn2 = byId("btn-2");
|
||||
|
||||
btn1.click();
|
||||
this.server.respond();
|
||||
ext1Calls.should.equal(1);
|
||||
ext2Calls.should.equal(1);
|
||||
ext3Calls.should.equal(0);
|
||||
|
||||
btn2.click();
|
||||
this.server.respond();
|
||||
ext1Calls.should.equal(2);
|
||||
ext2Calls.should.equal(1);
|
||||
ext3Calls.should.equal(1);
|
||||
});
|
||||
|
||||
it('supports comma separated lists', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
make('<div hx-ext="ext-1"><button id="btn-1" hx-get="/test" hx-ext="ext-2, ext-3 ">Click Me!</button></div>')
|
||||
var btn1 = byId("btn-1");
|
||||
var btn2 = byId("btn-2");
|
||||
|
||||
btn1.click();
|
||||
this.server.respond();
|
||||
ext1Calls.should.equal(1);
|
||||
ext2Calls.should.equal(1);
|
||||
ext3Calls.should.equal(1);
|
||||
});
|
||||
|
||||
it('A simple extension is invoked properly w/ data-* prefix', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button data-hx-get="/test" data-hx-ext="ext-1">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
ext1Calls.should.equal(1);
|
||||
ext2Calls.should.equal(0);
|
||||
ext3Calls.should.equal(0);
|
||||
});
|
||||
|
||||
|
||||
});
|
76
www/test/0.1.1/test/attributes/hx-get.js
Normal file
76
www/test/0.1.1/test/attributes/hx-get.js
Normal file
@ -0,0 +1,76 @@
|
||||
describe("hx-get attribute", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a GET request on click and swaps content', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('GET does not include surrounding data by default', function () {
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
should.equal(getParameters(xhr)["i1"], undefined);
|
||||
xhr.respond(200, {}, "Clicked!");
|
||||
});
|
||||
make('<form><input name="i1" value="value"/><button id="b1" hx-get="/test">Click Me!</button></form>')
|
||||
var btn = byId("b1");
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('GET on form includes its own data by default', function () {
|
||||
this.server.respondWith("GET", /\/test.*/, function (xhr) {
|
||||
getParameters(xhr)["i1"].should.equal("value");
|
||||
xhr.respond(200, {}, "Clicked!");
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-get="/test"><input name="i1" value="value"/><button id="b1">Click Me!</button></form>');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('GET on form with existing parameters works properly', function () {
|
||||
this.server.respondWith("GET", /\/test.*/, function (xhr) {
|
||||
getParameters(xhr)["foo"].should.equal("bar");
|
||||
getParameters(xhr)["i1"].should.equal("value");
|
||||
xhr.respond(200, {}, "Clicked!");
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-get="/test?foo=bar"><input name="i1" value="value"/><button id="b1">Click Me!</button></form>');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('GET on form with anchor works properly', function () {
|
||||
this.server.respondWith("GET", /\/test.*/, function (xhr) {
|
||||
getParameters(xhr)["foo"].should.equal("bar");
|
||||
getParameters(xhr)["i1"].should.equal("value");
|
||||
xhr.respond(200, {}, "Clicked!");
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-get="/test?foo=bar#foo"><input name="i1" value="value"/><button id="b1">Click Me!</button></form>');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
|
||||
it('issues a GET request on click and swaps content w/ data-* prefix', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button data-hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
});
|
153
www/test/0.1.1/test/attributes/hx-include.js
Normal file
153
www/test/0.1.1/test/attributes/hx-include.js
Normal file
@ -0,0 +1,153 @@
|
||||
describe("hx-include attribute", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('By default an input includes itself', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<div hx-target="this"><input hx-post="/include" hx-trigger="click" id="i1" name="i1" value="test"/></div>')
|
||||
var input = byId("i1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('non-GET includes closest form', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<form hx-target="this"><div id="d1" hx-post="/include"></div><input name="i1" value="test"/></form>')
|
||||
var input = byId("d1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('GET does not include closest form by default', function () {
|
||||
this.server.respondWith("GET", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
should.equal(params['i1'], undefined);
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<form hx-target="this"><div id="d1" hx-get="/include"></div><input name="i1" value="test"/></form>')
|
||||
var input = byId("d1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Input not included twice when in form', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<form hx-target="this"><input hx-post="/include" hx-trigger="click" id="i1" name="i1" value="test"/></form>')
|
||||
var input = byId("i1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Two inputs are included twice when they have the same name', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.deep.equal(["test", "test2"]);
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<form hx-target="this">' +
|
||||
'<input hx-post="/include" hx-trigger="click" id="i1" name="i1" value="test"/>' +
|
||||
'<input name="i1" value="test2"/>' +
|
||||
'</form>')
|
||||
var input = byId("i1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Input not included twice when it explicitly refers to parent form', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<form id="f1" hx-target="this">' +
|
||||
'<input hx-include="#f1" hx-post="/include" hx-trigger="click" id="i1" name="i1" value="test"/>' +
|
||||
'</form>')
|
||||
var input = byId("i1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Input can be referred to externally', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
make('<input id="i1" name="i1" value="test"/>');
|
||||
var div = make('<div hx-post="/include" hx-include="#i1"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Two inputs can be referred to externally', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
params['i2'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
make('<input id="i1" name="i1" value="test"/>');
|
||||
make('<input id="i2" name="i2" value="test"/>');
|
||||
var div = make('<div hx-post="/include" hx-include="#i1, #i2"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('A form can be referred to externally', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
params['i2'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
make('<form id="f1">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'</form> ');
|
||||
var div = make('<div hx-post="/include" hx-include="#f1"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('By default an input includes itself w/ data-* prefix', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<div data-hx-target="this"><input data-hx-post="/include" data-hx-trigger="click" id="i1" name="i1" value="test"/></div>')
|
||||
var input = byId("i1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
|
||||
});
|
52
www/test/0.1.1/test/attributes/hx-indicator.js
Normal file
52
www/test/0.1.1/test/attributes/hx-indicator.js
Normal file
@ -0,0 +1,52 @@
|
||||
describe("hx-indicator attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = sinon.fakeServer.create();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('Indicator classes are properly put on element with no explicit indicator', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
btn.classList.contains("htmx-request").should.equal(true);
|
||||
this.server.respond();
|
||||
btn.classList.contains("htmx-request").should.equal(false);
|
||||
});
|
||||
|
||||
it('Indicator classes are properly put on element with explicit indicator', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-indicator="#a1, #a2">Click Me!</button>')
|
||||
var a1 = make('<a id="a1"></a>')
|
||||
var a2 = make('<a id="a2"></a>')
|
||||
btn.click();
|
||||
btn.classList.contains("htmx-request").should.equal(false);
|
||||
a1.classList.contains("htmx-request").should.equal(true);
|
||||
a2.classList.contains("htmx-request").should.equal(true);
|
||||
this.server.respond();
|
||||
btn.classList.contains("htmx-request").should.equal(false);
|
||||
a1.classList.contains("htmx-request").should.equal(false);
|
||||
a2.classList.contains("htmx-request").should.equal(false);
|
||||
});
|
||||
|
||||
it('Indicator classes are properly put on element with explicit indicator w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" data-hx-indicator="#a1, #a2">Click Me!</button>')
|
||||
var a1 = make('<a id="a1"></a>')
|
||||
var a2 = make('<a id="a2"></a>')
|
||||
btn.click();
|
||||
btn.classList.contains("htmx-request").should.equal(false);
|
||||
a1.classList.contains("htmx-request").should.equal(true);
|
||||
a2.classList.contains("htmx-request").should.equal(true);
|
||||
this.server.respond();
|
||||
btn.classList.contains("htmx-request").should.equal(false);
|
||||
a1.classList.contains("htmx-request").should.equal(false);
|
||||
a2.classList.contains("htmx-request").should.equal(false);
|
||||
});
|
||||
})
|
101
www/test/0.1.1/test/attributes/hx-params.js
Normal file
101
www/test/0.1.1/test/attributes/hx-params.js
Normal file
@ -0,0 +1,101 @@
|
||||
describe("hx-params attribute", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('none excludes all params', function () {
|
||||
this.server.respondWith("POST", "/params", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
should.equal(params['i1'], undefined);
|
||||
should.equal(params['i2'], undefined);
|
||||
should.equal(params['i3'], undefined);
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-post="/params" hx-params="none">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'<input name="i3" value="test"/>' +
|
||||
'</form> ');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('"*" includes all params', function () {
|
||||
this.server.respondWith("POST", "/params", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
should.equal(params['i1'], "test");
|
||||
should.equal(params['i2'], "test");
|
||||
should.equal(params['i3'], "test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-post="/params" hx-params="*">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'<input name="i3" value="test"/>' +
|
||||
'</form> ');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('named includes works', function () {
|
||||
this.server.respondWith("POST", "/params", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
should.equal(params['i1'], "test");
|
||||
should.equal(params['i2'], undefined);
|
||||
should.equal(params['i3'], "test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-post="/params" hx-params="i1, i3">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'<input name="i3" value="test"/>' +
|
||||
'</form> ');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('named exclude works', function () {
|
||||
this.server.respondWith("POST", "/params", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
should.equal(params['i1'], undefined);
|
||||
should.equal(params['i2'], "test");
|
||||
should.equal(params['i3'], undefined);
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-post="/params" hx-params="not i1, i3">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'<input name="i3" value="test"/>' +
|
||||
'</form> ');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('named exclude works w/ data-* prefix', function () {
|
||||
this.server.respondWith("POST", "/params", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
should.equal(params['i1'], undefined);
|
||||
should.equal(params['i2'], "test");
|
||||
should.equal(params['i3'], undefined);
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var form = make('<form data-hx-trigger="click" data-hx-post="/params" data-hx-params="not i1, i3">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'<input name="i3" value="test"/>' +
|
||||
'</form> ');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
});
|
34
www/test/0.1.1/test/attributes/hx-patch.js
Normal file
34
www/test/0.1.1/test/attributes/hx-patch.js
Normal file
@ -0,0 +1,34 @@
|
||||
describe("hx-patch attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a PATCH request', function()
|
||||
{
|
||||
this.server.respondWith("PATCH", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Patched!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-patch="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Patched!");
|
||||
});
|
||||
|
||||
it('issues a PATCH request w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("PATCH", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Patched!");
|
||||
});
|
||||
|
||||
var btn = make('<button data-hx-patch="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Patched!");
|
||||
});
|
||||
})
|
36
www/test/0.1.1/test/attributes/hx-post.js
Normal file
36
www/test/0.1.1/test/attributes/hx-post.js
Normal file
@ -0,0 +1,36 @@
|
||||
describe("hx-post attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a POST request with proper headers', function()
|
||||
{
|
||||
this.server.respondWith("POST", "/test", function(xhr){
|
||||
should.equal(xhr.requestHeaders['X-HTTP-Method-Override'], undefined);
|
||||
xhr.respond(200, {}, "Posted!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-post="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Posted!");
|
||||
});
|
||||
|
||||
it('issues a POST request with proper headers w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("POST", "/test", function(xhr){
|
||||
should.equal(xhr.requestHeaders['X-HTTP-Method-Override'], undefined);
|
||||
xhr.respond(200, {}, "Posted!");
|
||||
});
|
||||
|
||||
var btn = make('<button data-hx-post="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Posted!");
|
||||
});
|
||||
})
|
170
www/test/0.1.1/test/attributes/hx-push-url.js
Normal file
170
www/test/0.1.1/test/attributes/hx-push-url.js
Normal file
@ -0,0 +1,170 @@
|
||||
describe("hx-push-url attribute", function() {
|
||||
|
||||
var HTMX_HISTORY_CACHE_NAME = "htmx-history-cache";
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
|
||||
it("navigation should push an element into the cache when true", function () {
|
||||
this.server.respondWith("GET", "/test", "second");
|
||||
getWorkArea().innerHTML.should.be.equal("");
|
||||
var div = make('<div hx-push-url="true" hx-get="/test">first</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.click();
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("second")
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
cache.length.should.equal(2);
|
||||
cache[1].url.should.equal("/test");
|
||||
});
|
||||
|
||||
it("navigation should push an element into the cache when string", function () {
|
||||
this.server.respondWith("GET", "/test", "second");
|
||||
getWorkArea().innerHTML.should.be.equal("");
|
||||
var div = make('<div hx-push-url="/abc123" hx-get="/test">first</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.click();
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("second")
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
console.log(cache);
|
||||
cache.length.should.equal(2);
|
||||
cache[1].url.should.equal("/abc123");
|
||||
});
|
||||
|
||||
it("restore should return old value", function () {
|
||||
this.server.respondWith("GET", "/test1", '<div id="d2" hx-push-url="true" hx-get="/test2" hx-swap="outerHTML settle:0">test1</div>');
|
||||
this.server.respondWith("GET", "/test2", '<div id="d3" hx-push-url="true" hx-get="/test3" hx-swap="outerHTML settle:0">test2</div>');
|
||||
|
||||
make('<div id="d1" hx-push-url="true" hx-get="/test1" hx-swap="outerHTML settle:0">init</div>');
|
||||
|
||||
byId("d1").click();
|
||||
this.server.respond();
|
||||
var workArea = getWorkArea();
|
||||
workArea.textContent.should.equal("test1")
|
||||
|
||||
byId("d2").click();
|
||||
this.server.respond();
|
||||
workArea.textContent.should.equal("test2")
|
||||
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
|
||||
cache.length.should.equal(2);
|
||||
htmx._('restoreHistory')("/test1")
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("test1")
|
||||
});
|
||||
|
||||
it("cache should only store 10 entries", function () {
|
||||
var x = 0;
|
||||
this.server.respondWith("GET", /test.*/, function(xhr){
|
||||
x++;
|
||||
xhr.respond(200, {}, '<div id="d1" hx-push-url="true" hx-get="/test' + x + '" hx-swap="outerHTML settle:0"></div>')
|
||||
});
|
||||
getWorkArea().innerHTML.should.be.equal("");
|
||||
make('<div id="d1" hx-push-url="true" hx-get="/test" hx-swap="outerHTML settle:0"></div>');
|
||||
for (var i = 0; i < 20; i++) { // issue 20 requests
|
||||
byId("d1").click();
|
||||
this.server.respond();
|
||||
}
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
cache.length.should.equal(10); // should only be 10 elements
|
||||
});
|
||||
|
||||
it("cache miss should issue another GET", function () {
|
||||
this.server.respondWith("GET", "/test1", '<div id="d2" hx-push-url="true" hx-get="/test2" hx-swap="outerHTML settle:0">test1</div>');
|
||||
this.server.respondWith("GET", "/test2", '<div id="d3" hx-push-url="true" hx-get="/test3" hx-swap="outerHTML settle:0">test2</div>');
|
||||
|
||||
make('<div id="d1" hx-push-url="true" hx-get="/test1" hx-swap="outerHTML settle:0">init</div>');
|
||||
|
||||
byId("d1").click();
|
||||
this.server.respond();
|
||||
var workArea = getWorkArea();
|
||||
workArea.textContent.should.equal("test1")
|
||||
|
||||
byId("d2").click();
|
||||
this.server.respond();
|
||||
workArea.textContent.should.equal("test2")
|
||||
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
|
||||
cache.length.should.equal(2);
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME); // clear cache
|
||||
htmx._('restoreHistory')("/test1")
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("test1")
|
||||
});
|
||||
|
||||
it("navigation should push an element into the cache w/ data-* prefix", function () {
|
||||
this.server.respondWith("GET", "/test", "second");
|
||||
getWorkArea().innerHTML.should.be.equal("");
|
||||
var div = make('<div data-hx-push-url="true" data-hx-get="/test">first</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("second")
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
cache.length.should.equal(1);
|
||||
});
|
||||
|
||||
it("deals with malformed JSON in history cache when getting", function () {
|
||||
localStorage.setItem(HTMX_HISTORY_CACHE_NAME, "Invalid JSON");
|
||||
var history = htmx._('getCachedHistory')('url');
|
||||
should.equal(history, null);
|
||||
});
|
||||
|
||||
it("deals with malformed JSON in history cache when saving", function () {
|
||||
localStorage.setItem(HTMX_HISTORY_CACHE_NAME, "Invalid JSON");
|
||||
htmx._('saveToHistoryCache')('url', 'content', 'title', 'scroll');
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
cache.length.should.equal(1);
|
||||
});
|
||||
|
||||
|
||||
it("htmx:afterSettle is called when replacing outerHTML", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("htmx:afterSettle", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var div = make("<button hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("htmx:afterSettle", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("should include parameters on a get", function () {
|
||||
var path = "";
|
||||
var handler = htmx.on("htmx:pushedIntoHistory", function (evt) {
|
||||
path = evt.detail.path;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("GET", /test.*/, function (xhr) {
|
||||
xhr.respond(200, {}, "second")
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-push-url="true" hx-get="/test"><input type="hidden" name="foo" value="bar"/>first</form>');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.textContent.should.equal("second")
|
||||
path.should.equal("/test?foo=bar")
|
||||
} finally {
|
||||
htmx.off("htmx:pushedIntoHistory", handler);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
34
www/test/0.1.1/test/attributes/hx-put.js
Normal file
34
www/test/0.1.1/test/attributes/hx-put.js
Normal file
@ -0,0 +1,34 @@
|
||||
describe("hx-put attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a PUT request', function()
|
||||
{
|
||||
this.server.respondWith("PUT", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Putted!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-put="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Putted!");
|
||||
});
|
||||
|
||||
it('issues a PUT request w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("PUT", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Putted!");
|
||||
});
|
||||
|
||||
var btn = make('<button data-hx-put="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Putted!");
|
||||
});
|
||||
})
|
40
www/test/0.1.1/test/attributes/hx-select.js
Normal file
40
www/test/0.1.1/test/attributes/hx-select.js
Normal file
@ -0,0 +1,40 @@
|
||||
describe("BOOTSTRAP - htmx AJAX Tests", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('properly handles a partial of HTML', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", "<div id='d1'>foo</div><div id='d2'>bar</div>");
|
||||
var div = make('<div hx-get="/test" hx-select="#d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("<div id=\"d1\">foo</div>");
|
||||
});
|
||||
|
||||
it('properly handles a full HTML document', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", "<html><body><div id='d1'>foo</div><div id='d2'>bar</div></body></html>");
|
||||
var div = make('<div hx-get="/test" hx-select="#d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("<div id=\"d1\">foo</div>");
|
||||
});
|
||||
|
||||
it('properly handles a full HTML document w/ data-* prefix', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", "<html><body><div id='d1'>foo</div><div id='d2'>bar</div></body></html>");
|
||||
var div = make('<div hx-get="/test" data-hx-select="#d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("<div id=\"d1\">foo</div>");
|
||||
});
|
||||
})
|
120
www/test/0.1.1/test/attributes/hx-sse.js
Normal file
120
www/test/0.1.1/test/attributes/hx-sse.js
Normal file
@ -0,0 +1,120 @@
|
||||
describe("hx-sse attribute", function() {
|
||||
|
||||
function mockEventSource() {
|
||||
var listeners = {};
|
||||
var wasClosed = false;
|
||||
var mockEventSource = {
|
||||
addEventListener: function (message, l) {
|
||||
listeners[message] = l;
|
||||
},
|
||||
sendEvent: function (event) {
|
||||
var listener = listeners[event];
|
||||
if (listener) {
|
||||
listener();
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
wasClosed = true;
|
||||
},
|
||||
wasClosed: function () {
|
||||
return wasClosed;
|
||||
}
|
||||
};
|
||||
return mockEventSource;
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
var eventSource = mockEventSource();
|
||||
this.eventSource = eventSource;
|
||||
clearWorkArea();
|
||||
htmx.createEventSource = function(){ return eventSource };
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('handles basic sse triggering', function () {
|
||||
|
||||
this.server.respondWith("GET", "/d1", "div1 updated");
|
||||
this.server.respondWith("GET", "/d2", "div2 updated");
|
||||
|
||||
var div = make('<div hx-sse="connect:/foo">' +
|
||||
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>' +
|
||||
'<div id="d2" hx-trigger="sse:e2" hx-get="/d2">div2</div>' +
|
||||
'</div>');
|
||||
|
||||
this.eventSource.sendEvent("e1");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1 updated");
|
||||
byId("d2").innerHTML.should.equal("div2");
|
||||
|
||||
this.eventSource.sendEvent("e2");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1 updated");
|
||||
byId("d2").innerHTML.should.equal("div2 updated");
|
||||
})
|
||||
|
||||
it('does not trigger events that arent named', function () {
|
||||
|
||||
this.server.respondWith("GET", "/d1", "div1 updated");
|
||||
|
||||
var div = make('<div hx-sse="connect:/foo">' +
|
||||
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>' +
|
||||
'</div>');
|
||||
|
||||
this.eventSource.sendEvent("foo");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1");
|
||||
|
||||
this.eventSource.sendEvent("e2");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1");
|
||||
|
||||
this.eventSource.sendEvent("e1");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1 updated");
|
||||
})
|
||||
|
||||
it('does not trigger events not on decendents', function () {
|
||||
|
||||
this.server.respondWith("GET", "/d1", "div1 updated");
|
||||
|
||||
var div = make('<div hx-sse="connect:/foo"></div>' +
|
||||
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>');
|
||||
|
||||
this.eventSource.sendEvent("foo");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1");
|
||||
|
||||
this.eventSource.sendEvent("e2");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1");
|
||||
|
||||
this.eventSource.sendEvent("e1");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1");
|
||||
})
|
||||
|
||||
it('is closed after removal', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div = make('<div hx-get="/test" hx-swap="outerHTML" hx-sse="connect:/foo">' +
|
||||
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>' +
|
||||
'</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
this.eventSource.wasClosed().should.equal(true)
|
||||
})
|
||||
|
||||
it('is closed after removal with no close and activity', function () {
|
||||
var div = make('<div hx-get="/test" hx-swap="outerHTML" hx-sse="connect:/foo">' +
|
||||
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>' +
|
||||
'</div>');
|
||||
div.parentElement.removeChild(div);
|
||||
this.eventSource.sendEvent("e1")
|
||||
this.eventSource.wasClosed().should.equal(true)
|
||||
})
|
||||
|
||||
});
|
||||
|
75
www/test/0.1.1/test/attributes/hx-swap-oob.js
Normal file
75
www/test/0.1.1/test/attributes/hx-swap-oob.js
Normal file
@ -0,0 +1,75 @@
|
||||
describe("hx-swap-oob attribute", function () {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('handles basic response properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' hx-swap-oob='true'>Swapped</div>");
|
||||
var div = make('<div hx-get="/test">click me</div>');
|
||||
make('<div id="d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked");
|
||||
byId("d1").innerHTML.should.equal("Swapped");
|
||||
})
|
||||
|
||||
it('handles more than one oob swap properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' hx-swap-oob='true'>Swapped1</div><div id='d2' hx-swap-oob='true'>Swapped2</div>");
|
||||
var div = make('<div hx-get="/test">click me</div>');
|
||||
make('<div id="d1"></div>');
|
||||
make('<div id="d2"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked");
|
||||
byId("d1").innerHTML.should.equal("Swapped1");
|
||||
byId("d2").innerHTML.should.equal("Swapped2");
|
||||
})
|
||||
|
||||
it('handles no id match properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' hx-swap-oob='true'>Swapped</div>");
|
||||
var div = make('<div hx-get="/test">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("Clicked");
|
||||
})
|
||||
|
||||
it('handles basic response properly w/ data-* prefix', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' data-hx-swap-oob='true'>Swapped</div>");
|
||||
var div = make('<div data-hx-get="/test">click me</div>');
|
||||
make('<div id="d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked");
|
||||
byId("d1").innerHTML.should.equal("Swapped");
|
||||
})
|
||||
|
||||
it('handles outerHTML response properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' foo='bar' hx-swap-oob='outerHTML'>Swapped</div>");
|
||||
var div = make('<div hx-get="/test">click me</div>');
|
||||
make('<div id="d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
byId("d1").getAttribute("foo").should.equal("bar");
|
||||
div.innerHTML.should.equal("Clicked");
|
||||
byId("d1").innerHTML.should.equal("Swapped");
|
||||
})
|
||||
|
||||
it('handles innerHTML response properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' foo='bar' hx-swap-oob='innerHTML'>Swapped</div>");
|
||||
var div = make('<div hx-get="/test">click me</div>');
|
||||
make('<div id="d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(byId("d1").getAttribute("foo"), null);
|
||||
div.innerHTML.should.equal("Clicked");
|
||||
byId("d1").innerHTML.should.equal("Swapped");
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
274
www/test/0.1.1/test/attributes/hx-swap.js
Normal file
274
www/test/0.1.1/test/attributes/hx-swap.js
Normal file
@ -0,0 +1,274 @@
|
||||
describe("hx-swap attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('swap innerHTML properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a hx-get="/test2">Click Me</a>');
|
||||
this.server.respondWith("GET", "/test2", "Clicked!");
|
||||
|
||||
var div = make('<div hx-get="/test"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal('<a hx-get="/test2">Click Me</a>');
|
||||
var a = div.querySelector('a');
|
||||
a.click();
|
||||
this.server.respond();
|
||||
a.innerHTML.should.equal('Clicked!');
|
||||
});
|
||||
|
||||
it('swap outerHTML properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a id="a1" hx-get="/test2">Click Me</a>');
|
||||
this.server.respondWith("GET", "/test2", "Clicked!");
|
||||
|
||||
var div = make('<div id="d1" hx-get="/test" hx-swap="outerHTML"></div>')
|
||||
div.click();
|
||||
should.equal(byId("d1"), div);
|
||||
this.server.respond();
|
||||
should.equal(byId("d1"), null);
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
byId("a1").innerHTML.should.equal('Clicked!');
|
||||
});
|
||||
|
||||
it('swap beforebegin properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforebegin">*</div>')
|
||||
var parent = div.parentElement;
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("1*");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("***");
|
||||
});
|
||||
|
||||
it('swap afterbegin properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterbegin">*</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1*");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("2**");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("***");
|
||||
});
|
||||
|
||||
it('swap afterbegin properly with no initial content', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterbegin"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
});
|
||||
|
||||
it('swap afterend properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterend">*</div>')
|
||||
var parent = div.parentElement;
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("***");
|
||||
});
|
||||
|
||||
it('swap beforeend properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforeend">*</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**2");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("***");
|
||||
});
|
||||
|
||||
it('swap beforeend properly with no initial content', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforeend"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*2");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
});
|
||||
|
||||
it('properly parses various swap specifications', function(){
|
||||
var swapSpec = htmx._("getSwapSpecification"); // internal function for swap spec
|
||||
swapSpec(make("<div/>")).swapStyle.should.equal("innerHTML")
|
||||
swapSpec(make("<div hx-swap='innerHTML'/>")).swapStyle.should.equal("innerHTML")
|
||||
swapSpec(make("<div hx-swap='innerHTML'/>")).swapDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='innerHTML'/>")).settleDelay.should.equal(0) // set to 0 in tests
|
||||
swapSpec(make("<div hx-swap='innerHTML swap:10'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:10'/>")).settleDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML swap:10 settle:11'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML swap:10 settle:11'/>")).settleDelay.should.equal(11)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:11 swap:10'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:11 swap:10'/>")).settleDelay.should.equal(11)
|
||||
swapSpec(make("<div hx-swap='innerHTML nonsense settle:11 swap:10'/>")).settleDelay.should.equal(11)
|
||||
swapSpec(make("<div hx-swap='innerHTML nonsense settle:11 swap:10 '/>")).settleDelay.should.equal(11)
|
||||
})
|
||||
|
||||
it('works with a swap delay', function(done) {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div = make("<div hx-get='/test' hx-swap='innerHTML swap:10ms'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("");
|
||||
setTimeout(function () {
|
||||
div.innerText.should.equal("Clicked!");
|
||||
done();
|
||||
}, 30);
|
||||
});
|
||||
|
||||
it('works with a settle delay', function(done) {
|
||||
this.server.respondWith("GET", "/test", "<div id='d1' class='foo' hx-get='/test' hx-swap='outerHTML settle:10ms'></div>");
|
||||
var div = make("<div id='d1' hx-get='/test' hx-swap='outerHTML settle:10ms'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.classList.contains('foo').should.equal(false);
|
||||
setTimeout(function () {
|
||||
byId('d1').classList.contains('foo').should.equal(true);
|
||||
done();
|
||||
}, 30);
|
||||
});
|
||||
|
||||
it('swap outerHTML properly w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a id="a1" data-hx-get="/test2">Click Me</a>');
|
||||
this.server.respondWith("GET", "/test2", "Clicked!");
|
||||
|
||||
var div = make('<div id="d1" data-hx-get="/test" data-hx-swap="outerHTML"></div>')
|
||||
div.click();
|
||||
should.equal(byId("d1"), div);
|
||||
this.server.respond();
|
||||
should.equal(byId("d1"), null);
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
byId("a1").innerHTML.should.equal('Clicked!');
|
||||
});
|
||||
|
||||
it('swap none works properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", 'Ooops, swapped');
|
||||
|
||||
var div = make('<div hx-swap="none" hx-get="/test">Foo</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal('Foo');
|
||||
});
|
||||
|
||||
})
|
95
www/test/0.1.1/test/attributes/hx-target.js
Normal file
95
www/test/0.1.1/test/attributes/hx-target.js
Normal file
@ -0,0 +1,95 @@
|
||||
describe("hx-target attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = sinon.fakeServer.create();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('targets an adjacent element properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-target="#d1" hx-get="/test">Click Me!</button>')
|
||||
var div1 = make('<div id="d1"></div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('targets a parent element properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div1 = make('<div id="d1"><button id="b1" hx-target="#d1" hx-get="/test">Click Me!</button></div>')
|
||||
var btn = byId("b1")
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('targets a `this` element properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div1 = make('<div hx-target="this"><button id="b1" hx-get="/test">Click Me!</button></div>')
|
||||
var btn = byId("b1")
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('targets a `closest` element properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div1 = make('<div><p><i><button id="b1" hx-target="closest div" hx-get="/test">Click Me!</button></i></p></div>')
|
||||
var btn = byId("b1")
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('targets a `find` element properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div1 = make('<div hx-target="find span" hx-get="/test">Click Me! <div><span id="s1"></span><span id="s2"></span></div></div>')
|
||||
div1.click();
|
||||
this.server.respond();
|
||||
var span1 = byId("s1")
|
||||
var span2 = byId("s2")
|
||||
span1.innerHTML.should.equal("Clicked!");
|
||||
span2.innerHTML.should.equal("");
|
||||
});
|
||||
|
||||
it('targets an inner element properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-target="#d1" hx-get="/test">Click Me!<div id="d1"></div></button>')
|
||||
var div1 = byId("d1")
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
|
||||
it('handles bad target gracefully', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-target="bad" hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Click Me!");
|
||||
});
|
||||
|
||||
|
||||
it('targets an adjacent element properly w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button data-hx-target="#d1" data-hx-get="/test">Click Me!</button>')
|
||||
var div1 = make('<div id="d1"></div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
|
||||
})
|
160
www/test/0.1.1/test/attributes/hx-trigger.js
Normal file
160
www/test/0.1.1/test/attributes/hx-trigger.js
Normal file
@ -0,0 +1,160 @@
|
||||
describe("hx-trigger attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = sinon.fakeServer.create();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('non-default value works', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var form = make('<form hx-get="/test" hx-trigger="click">Click Me!</form>');
|
||||
form.click();
|
||||
form.innerHTML.should.equal("Click Me!");
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('changed modifier works', function()
|
||||
{
|
||||
var requests = 0;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
requests++;
|
||||
xhr.respond(200, {}, "Requests: " + requests);
|
||||
});
|
||||
var input = make('<input hx-trigger="click changed" hx-target="#d1" hx-get="/test" value="foo"/>');
|
||||
var div = make('<div id="d1"></div>');
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("");
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("");
|
||||
input.value = "bar";
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
});
|
||||
|
||||
it('once modifier works', function()
|
||||
{
|
||||
var requests = 0;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
requests++;
|
||||
xhr.respond(200, {}, "Requests: " + requests);
|
||||
});
|
||||
var input = make('<input hx-trigger="click once" hx-target="#d1" hx-get="/test" value="foo"/>');
|
||||
var div = make('<div id="d1"></div>');
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
input.value = "bar";
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
});
|
||||
|
||||
it('polling works', function(complete)
|
||||
{
|
||||
var requests = 0;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
requests++;
|
||||
if (requests > 5) {
|
||||
complete();
|
||||
// cancel polling with a
|
||||
xhr.respond(286, {}, "Requests: " + requests);
|
||||
} else {
|
||||
xhr.respond(200, {}, "Requests: " + requests);
|
||||
}
|
||||
});
|
||||
this.server.autoRespond = true;
|
||||
this.server.autoRespondAfter = 0;
|
||||
make('<div hx-trigger="every 10ms" hx-get="/test"/>');
|
||||
});
|
||||
|
||||
|
||||
it('non-default value works w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var form = make('<form data-hx-get="/test" data-hx-trigger="click">Click Me!</form>');
|
||||
form.click();
|
||||
form.innerHTML.should.equal("Click Me!");
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with multiple events', function()
|
||||
{
|
||||
var requests = 0;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
requests++;
|
||||
xhr.respond(200, {}, "Requests: " + requests);
|
||||
});
|
||||
var div = make('<div hx-trigger="load,click" hx-get="/test">Requests: 0</div>');
|
||||
div.innerHTML.should.equal("Requests: 0");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 2");
|
||||
});
|
||||
|
||||
|
||||
|
||||
it("parses spec strings", function()
|
||||
{
|
||||
var specExamples = {
|
||||
"": [{trigger: 'click'}],
|
||||
"every 1s": [{trigger: 'every', pollInterval: 1000}],
|
||||
"click": [{trigger: 'click'}],
|
||||
"customEvent": [{trigger: 'customEvent'}],
|
||||
"event changed": [{trigger: 'event', changed: true}],
|
||||
"event once": [{trigger: 'event', once: true}],
|
||||
"event delay:1s": [{trigger: 'event', delay: 1000}],
|
||||
"event throttle:1s": [{trigger: 'event', throttle: 1000}],
|
||||
"event changed once delay:1s": [{trigger: 'event', changed: true, once: true, delay: 1000}],
|
||||
"event1,event2": [{trigger: 'event1'}, {trigger: 'event2'}],
|
||||
"event1, event2": [{trigger: 'event1'}, {trigger: 'event2'}],
|
||||
"event1 once, event2 changed": [{trigger: 'event1', once: true}, {trigger: 'event2', changed: true}],
|
||||
"event1,": [{trigger: 'event1'}],
|
||||
",event1": [{trigger: 'event1'}],
|
||||
" ": [{trigger: 'click'}],
|
||||
",": [{trigger: 'click'}]
|
||||
}
|
||||
|
||||
for (var specString in specExamples) {
|
||||
var div = make("<div hx-trigger='" + specString + "'></div>");
|
||||
var spec = htmx._('getTriggerSpecs')(div);
|
||||
spec.should.deep.equal(specExamples[specString], "Found : " + JSON.stringify(spec) + ", expected : " + JSON.stringify(specExamples[specString]) + " for spec: " + specString);
|
||||
}
|
||||
});
|
||||
|
||||
it('sets default trigger for forms', function()
|
||||
{
|
||||
var form = make('<form></form>');
|
||||
var spec = htmx._('getTriggerSpecs')(form);
|
||||
spec.should.deep.equal([{trigger: 'submit'}]);
|
||||
})
|
||||
|
||||
it('sets default trigger for form elements', function()
|
||||
{
|
||||
var form = make('<input></input>');
|
||||
var spec = htmx._('getTriggerSpecs')(form);
|
||||
spec.should.deep.equal([{trigger: 'change'}]);
|
||||
})
|
||||
|
||||
})
|
75
www/test/0.1.1/test/attributes/hx-vars.js
Normal file
75
www/test/0.1.1/test/attributes/hx-vars.js
Normal file
@ -0,0 +1,75 @@
|
||||
describe("hx-vars attribute", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('basic hx-vars works', function () {
|
||||
this.server.respondWith("POST", "/vars", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<div hx-post="/vars" hx-vars="i1:\'test\'"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('multiple hx-vars works', function () {
|
||||
this.server.respondWith("POST", "/vars", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['v1'].should.equal("test");
|
||||
params['v2'].should.equal("42");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<div hx-post="/vars" hx-vars="v1:\'test\', v2:42"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('hx-vars can be on parents', function () {
|
||||
this.server.respondWith("POST", "/vars", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
make('<div hx-vars="i1:\'test\'"><div id="d1" hx-post="/vars"></div></div>')
|
||||
var div = byId("d1");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('hx-vars can override parents', function () {
|
||||
this.server.respondWith("POST", "/vars", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("best");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
make('<div hx-vars="i1:\'test\'"><div id="d1" hx-vars="i1:\'best\'" hx-post="/vars"></div></div>')
|
||||
var div = byId("d1");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('hx-vars do not override inputs', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<div hx-target="this"><input hx-post="/include" hx-vars="i1:\'best\'" hx-trigger="click" id="i1" name="i1" value="test"/></div>')
|
||||
var input = byId("i1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
});
|
73
www/test/0.1.1/test/attributes/hx-ws.js
Normal file
73
www/test/0.1.1/test/attributes/hx-ws.js
Normal file
@ -0,0 +1,73 @@
|
||||
describe("hx-ws attribute", function() {
|
||||
|
||||
function mockWebsocket() {
|
||||
var listener;
|
||||
var lastSent;
|
||||
var wasClosed = false;
|
||||
var mockSocket = {
|
||||
addEventListener : function(message, l) {
|
||||
listener = l;
|
||||
},
|
||||
write : function(content) {
|
||||
return listener({data:content});
|
||||
},
|
||||
send : function(data) {
|
||||
lastSent = data;
|
||||
},
|
||||
getLastSent : function() {
|
||||
return lastSent;
|
||||
},
|
||||
close : function() {
|
||||
wasClosed = true;
|
||||
},
|
||||
wasClosed : function () {
|
||||
return wasClosed;
|
||||
}
|
||||
};
|
||||
return mockSocket;
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
var socket = mockWebsocket();
|
||||
this.socket = socket;
|
||||
clearWorkArea();
|
||||
htmx.createWebSocket = function(){ return socket };
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('handles a basic call back', function () {
|
||||
var div = make('<div hx-ws="connect:/foo"><div id="d1">div1</div><div id="d2">div2</div></div>');
|
||||
this.socket.write("<div id=\"d1\">replaced</div>")
|
||||
byId("d1").innerHTML.should.equal("replaced");
|
||||
byId("d2").innerHTML.should.equal("div2");
|
||||
})
|
||||
|
||||
it('handles a basic send', function () {
|
||||
var div = make('<div hx-ws="connect:/foo"><div hx-ws="send" id="d1">div1</div></div>');
|
||||
byId("d1").click();
|
||||
var lastSent = this.socket.getLastSent();
|
||||
var data = JSON.parse(lastSent);
|
||||
data.HEADERS["HX-Request"].should.equal("true");
|
||||
})
|
||||
|
||||
it('is closed after removal', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div = make('<div hx-get="/test" hx-swap="outerHTML" hx-ws="connect:wss:/foo"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
this.socket.wasClosed().should.equal(true)
|
||||
})
|
||||
|
||||
it('is closed after removal with no close and activity', function () {
|
||||
var div = make('<div hx-ws="connect:/foo"></div>');
|
||||
div.parentElement.removeChild(div);
|
||||
this.socket.write("<div id=\"d1\">replaced</div>")
|
||||
this.socket.wasClosed().should.equal(true)
|
||||
})
|
||||
|
||||
});
|
||||
|
589
www/test/0.1.1/test/core/ajax.js
Normal file
589
www/test/0.1.1/test/core/ajax.js
Normal file
@ -0,0 +1,589 @@
|
||||
describe("Core htmx AJAX Tests", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
// bootstrap test
|
||||
it('issues a GET request on click and swaps content', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('processes inner content properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a hx-get="/test2">Click Me</a>');
|
||||
this.server.respondWith("GET", "/test2", "Clicked!");
|
||||
|
||||
var div = make('<div hx-get="/test"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal('<a hx-get="/test2">Click Me</a>');
|
||||
var a = div.querySelector('a');
|
||||
a.click();
|
||||
this.server.respond();
|
||||
a.innerHTML.should.equal('Clicked!');
|
||||
});
|
||||
|
||||
it('handles swap outerHTML properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a id="a1" hx-get="/test2">Click Me</a>');
|
||||
this.server.respondWith("GET", "/test2", "Clicked!");
|
||||
|
||||
var div = make('<div id="d1" hx-get="/test" hx-swap="outerHTML"></div>')
|
||||
div.click();
|
||||
should.equal(byId("d1"), div);
|
||||
this.server.respond();
|
||||
should.equal(byId("d1"), null);
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
byId("a1").innerHTML.should.equal('Clicked!');
|
||||
});
|
||||
|
||||
it('handles beforebegin properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforebegin">*</div>')
|
||||
var parent = div.parentElement;
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("1*");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("***");
|
||||
});
|
||||
|
||||
it('handles afterbegin properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterbegin">*</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1*");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("2**");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("***");
|
||||
});
|
||||
|
||||
it('handles afterbegin properly with no initial content', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterbegin"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
});
|
||||
|
||||
it('handles afterend properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterend">*</div>')
|
||||
var parent = div.parentElement;
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("***");
|
||||
});
|
||||
|
||||
it('handles beforeend properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforeend">*</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**2");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("***");
|
||||
});
|
||||
|
||||
it('handles beforeend properly with no initial content', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforeend"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*2");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
});
|
||||
|
||||
it('handles hx-target properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button hx-get="/test" hx-target="#s1">Click Me!</button>');
|
||||
var target = make('<span id="s1">Initial</span>');
|
||||
btn.click();
|
||||
target.innerHTML.should.equal("Initial");
|
||||
this.server.respond();
|
||||
target.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('handles 204 NO CONTENT responses properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", [204, {}, "No Content!"]);
|
||||
|
||||
var btn = make('<button hx-get="/test">Click Me!</button>');
|
||||
btn.click();
|
||||
btn.innerHTML.should.equal("Click Me!");
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Click Me!");
|
||||
});
|
||||
|
||||
it('handles 304 NOT MODIFIED responses properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test-1", [200, {}, "Content for Tab 1"]);
|
||||
this.server.respondWith("GET", "/test-2", [200, {}, "Content for Tab 2"]);
|
||||
|
||||
var target = make('<div id="target"></div>')
|
||||
var btn1 = make('<button hx-get="/test-1" hx-target="#target">Tab 1</button>');
|
||||
var btn2 = make('<button hx-get="/test-2" hx-target="#target">Tab 2</button>');
|
||||
|
||||
btn1.click();
|
||||
target.innerHTML.should.equal("");
|
||||
this.server.respond();
|
||||
target.innerHTML.should.equal("Content for Tab 1");
|
||||
|
||||
btn2.click();
|
||||
this.server.respond();
|
||||
target.innerHTML.should.equal("Content for Tab 2");
|
||||
|
||||
this.server.respondWith("GET", "/test-1", [304, {}, "Content for Tab 1"]);
|
||||
this.server.respondWith("GET", "/test-2", [304, {}, "Content for Tab 2"]);
|
||||
|
||||
btn1.click();
|
||||
this.server.respond();
|
||||
target.innerHTML.should.equal("Content for Tab 1");
|
||||
|
||||
btn2.click();
|
||||
this.server.respond();
|
||||
target.innerHTML.should.equal("Content for Tab 2");
|
||||
});
|
||||
|
||||
it('handles hx-trigger with non-default value', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var form = make('<form hx-get="/test" hx-trigger="click">Click Me!</form>');
|
||||
form.click();
|
||||
form.innerHTML.should.equal("Click Me!");
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('handles hx-trigger with load event', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Loaded!");
|
||||
var div = make('<div hx-get="/test" hx-trigger="load">Load Me!</div>');
|
||||
div.innerHTML.should.equal("Load Me!");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Loaded!");
|
||||
});
|
||||
|
||||
it('sets the content type of the request properly', function (done) {
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "done");
|
||||
xhr.overriddenMimeType.should.equal("text/html");
|
||||
done();
|
||||
});
|
||||
var div = make('<div hx-get="/test">Click Me!</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
});
|
||||
|
||||
it('issues two requests when clicked twice before response', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "click " + i);
|
||||
i++
|
||||
});
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
div.click();
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("click 1");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("click 2");
|
||||
});
|
||||
|
||||
it('issues two requests when clicked three times before response', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "click " + i);
|
||||
i++
|
||||
});
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
div.click();
|
||||
div.click();
|
||||
div.click();
|
||||
this.server.respondAll();
|
||||
div.innerHTML.should.equal("click 2");
|
||||
});
|
||||
|
||||
it('properly handles hx-select for basic situation', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", "<div id='d1'>foo</div><div id='d2'>bar</div>");
|
||||
var div = make('<div hx-get="/test" hx-select="#d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("<div id=\"d1\">foo</div>");
|
||||
});
|
||||
|
||||
it('properly handles hx-select for full html document situation', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "<html><body><div id='d1'>foo</div><div id='d2'>bar</div></body></html>");
|
||||
var div = make('<div hx-get="/test" hx-select="#d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("<div id=\"d1\">foo</div>");
|
||||
});
|
||||
|
||||
it('properly settles attributes on interior elements', function(done)
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "<div hx-get='/test'><div width='bar' id='d1'></div></div>");
|
||||
var div = make("<div hx-get='/test' hx-swap='outerHTML settle:10ms'><div id='d1'></div></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(byId("d1").getAttribute("width"), null);
|
||||
setTimeout(function () {
|
||||
should.equal(byId("d1").getAttribute("width"), "bar");
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
it('properly handles multiple select input', function()
|
||||
{
|
||||
var values;
|
||||
this.server.respondWith("Post", "/test", function (xhr) {
|
||||
values = getParameters(xhr);
|
||||
xhr.respond(204, {}, "");
|
||||
});
|
||||
|
||||
var form = make('<form hx-post="/test" hx-trigger="click">' +
|
||||
'<select id="multiSelect" name="multiSelect" multiple="multiple">'+
|
||||
'<option id="m1" value="m1">m1</option>'+
|
||||
'<option id="m2" value="m2">m2</option>'+
|
||||
'<option id="m3" value="m3">m3</option>'+
|
||||
'<option id="m4" value="m4">m4</option>'+
|
||||
'</form>');
|
||||
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({});
|
||||
|
||||
byId("m1").selected = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({multiSelect:"m1"});
|
||||
|
||||
byId("m1").selected = true;
|
||||
byId("m3").selected = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({multiSelect:["m1", "m3"]});
|
||||
});
|
||||
|
||||
it('properly handles checkbox inputs', function()
|
||||
{
|
||||
var values;
|
||||
this.server.respondWith("Post", "/test", function (xhr) {
|
||||
values = getParameters(xhr);
|
||||
xhr.respond(204, {}, "");
|
||||
});
|
||||
|
||||
var form = make('<form hx-post="/test" hx-trigger="click">' +
|
||||
'<input id="cb1" name="c1" value="cb1" type="checkbox">'+
|
||||
'<input id="cb2" name="c1" value="cb2" type="checkbox">'+
|
||||
'<input id="cb3" name="c1" value="cb3" type="checkbox">'+
|
||||
'<input id="cb4" name="c2" value="cb4" type="checkbox">'+
|
||||
'<input id="cb5" name="c2" value="cb5" type="checkbox">'+
|
||||
'<input id="cb6" name="c3" value="cb6" type="checkbox">'+
|
||||
'</form>');
|
||||
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:"cb1"});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb2"]});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = true;
|
||||
byId("cb3").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb2", "cb3"]});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = true;
|
||||
byId("cb3").checked = true;
|
||||
byId("cb4").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb2", "cb3"], c2:"cb4"});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = true;
|
||||
byId("cb3").checked = true;
|
||||
byId("cb4").checked = true;
|
||||
byId("cb5").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb2", "cb3"], c2:["cb4", "cb5"]});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = true;
|
||||
byId("cb3").checked = true;
|
||||
byId("cb4").checked = true;
|
||||
byId("cb5").checked = true;
|
||||
byId("cb6").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb2", "cb3"], c2:["cb4", "cb5"], c3:"cb6"});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = false;
|
||||
byId("cb3").checked = true;
|
||||
byId("cb4").checked = false;
|
||||
byId("cb5").checked = true;
|
||||
byId("cb6").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb3"], c2:"cb5", c3:"cb6"});
|
||||
|
||||
});
|
||||
|
||||
it('text nodes dont screw up settling via variable capture', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "<div id='d1' hx-get='/test2'></div>fooo");
|
||||
this.server.respondWith("GET", "/test2", "clicked");
|
||||
var div = make("<div hx-get='/test'/>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
byId("d1").click();
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("clicked");
|
||||
});
|
||||
|
||||
var globalWasCalled = false;
|
||||
window.callGlobal = function() {
|
||||
globalWasCalled = true;
|
||||
}
|
||||
|
||||
it('script nodes evaluate', function()
|
||||
{
|
||||
try {
|
||||
this.server.respondWith("GET", "/test", "<div></div><script type='text/javascript'>callGlobal()</script>");
|
||||
var div = make("<div hx-get='/test'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
globalWasCalled.should.equal(true);
|
||||
} finally {
|
||||
delete window.callGlobal;
|
||||
}
|
||||
});
|
||||
|
||||
it('script node exceptions do not break rendering', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "clicked<script type='text/javascript'>throw 'foo';</script>");
|
||||
var div = make("<div hx-get='/test'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("clicked");
|
||||
});
|
||||
|
||||
it('allows empty verb values', function()
|
||||
{
|
||||
var path = null;
|
||||
var div = make("<div hx-get=''/>");
|
||||
htmx.on(div, "htmx:configRequest", function (evt) {
|
||||
path = evt.detail.path;
|
||||
return false;
|
||||
});
|
||||
div.click();
|
||||
this.server.respond();
|
||||
path.should.not.be.null;
|
||||
});
|
||||
|
||||
it('allows blank verb values', function()
|
||||
{
|
||||
var path = null;
|
||||
var div = make("<div hx-get/>");
|
||||
htmx.on(div, "htmx:configRequest", function (evt) {
|
||||
path = evt.detail.path;
|
||||
return false;
|
||||
});
|
||||
div.click();
|
||||
this.server.respond();
|
||||
path.should.not.be.null;
|
||||
});
|
||||
|
||||
it('input values are not settle swapped (causes flicker)', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "<input id='i1' value='bar'/>");
|
||||
var input = make("<input id='i1' hx-get='/test' value='foo' hx-swap='outerHTML settle:50' hx-trigger='click'/>");
|
||||
input.click();
|
||||
this.server.respond();
|
||||
input = byId('i1');
|
||||
input.value.should.equal('bar');
|
||||
});
|
||||
|
||||
it('autofocus attribute works properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "<input id='i2' value='bar' autofocus/>");
|
||||
var input = make("<input id='i1' hx-get='/test' value='foo' hx-swap='afterend' hx-trigger='click'/>");
|
||||
input.focus();
|
||||
input.click();
|
||||
document.activeElement.should.equal(input);
|
||||
this.server.respond();
|
||||
var input2 = byId('i2');
|
||||
document.activeElement.should.equal(input2);
|
||||
});
|
||||
|
||||
it('autofocus attribute works properly w/ child', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "<div><input id='i2' value='bar' autofocus/></div>");
|
||||
var input = make("<input id='i1' hx-get='/test' value='foo' hx-swap='afterend' hx-trigger='click'/>");
|
||||
input.focus();
|
||||
input.click();
|
||||
document.activeElement.should.equal(input);
|
||||
this.server.respond();
|
||||
var input2 = byId('i2');
|
||||
document.activeElement.should.equal(input2);
|
||||
});
|
||||
|
||||
it('autofocus attribute works properly w/ true value', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "<div><input id='i2' value='bar' autofocus='true'/></div>");
|
||||
var input = make("<input id='i1' hx-get='/test' value='foo' hx-swap='afterend' hx-trigger='click'/>");
|
||||
input.focus();
|
||||
input.click();
|
||||
document.activeElement.should.equal(input);
|
||||
this.server.respond();
|
||||
var input2 = byId('i2');
|
||||
document.activeElement.should.equal(input2);
|
||||
});
|
||||
|
||||
|
||||
})
|
167
www/test/0.1.1/test/core/api.js
Normal file
167
www/test/0.1.1/test/core/api.js
Normal file
@ -0,0 +1,167 @@
|
||||
describe("Core htmx API test", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('onLoad is called... onLoad', function(){
|
||||
// also tests on/off
|
||||
this.server.respondWith("GET", "/test", "<div id='d1' hx-get='/test'></div>")
|
||||
var helper = htmx.onLoad(function (elt) {
|
||||
elt.setAttribute("foo", "bar");
|
||||
});
|
||||
try {
|
||||
var div = make("<div id='d1' hx-get='/test' hx-swap='outerHTML'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
byId("d1").getAttribute("foo").should.equal("bar");
|
||||
} finally {
|
||||
htmx.off("htmx:load", helper);
|
||||
}
|
||||
});
|
||||
|
||||
it('triggers properly', function () {
|
||||
var div = make("<div/>");
|
||||
var myEventCalled = false;
|
||||
var detailStr = "";
|
||||
htmx.on("myEvent", function(evt){
|
||||
myEventCalled = true;
|
||||
detailStr = evt.detail.str;
|
||||
})
|
||||
htmx.trigger(div, "myEvent", {str:"foo"})
|
||||
|
||||
myEventCalled.should.equal(true);
|
||||
detailStr.should.equal("foo");
|
||||
});
|
||||
|
||||
it('triggers with no details properly', function () {
|
||||
var div = make("<div/>");
|
||||
var myEventCalled = false;
|
||||
htmx.on("myEvent", function(evt){
|
||||
myEventCalled = true;
|
||||
})
|
||||
htmx.trigger(div, "myEvent")
|
||||
myEventCalled.should.equal(true);
|
||||
});
|
||||
|
||||
it('should find properly', function(){
|
||||
var div = make("<div id='d1' class='c1 c2'>");
|
||||
div.should.equal(htmx.find("#d1"));
|
||||
div.should.equal(htmx.find(".c1"));
|
||||
div.should.equal(htmx.find(".c2"));
|
||||
div.should.equal(htmx.find(".c1.c2"));
|
||||
});
|
||||
|
||||
it('should find properly from elt', function(){
|
||||
var div = make("<div><a id='a1'></a><a id='a2'></a></div>");
|
||||
htmx.find(div, "a").id.should.equal('a1');
|
||||
});
|
||||
|
||||
it('should find all properly', function(){
|
||||
var div = make("<div class='c1 c2 c3'><div class='c1 c2'><div class='c1'>");
|
||||
htmx.findAll(".c1").length.should.equal(3);
|
||||
htmx.findAll(".c2").length.should.equal(2);
|
||||
htmx.findAll(".c3").length.should.equal(1);
|
||||
});
|
||||
|
||||
it('should find all properly from elt', function(){
|
||||
var div = make("<div><div class='c1 c2 c3'><div class='c1 c2'><div class='c1'></div>");
|
||||
htmx.findAll(div, ".c1").length.should.equal(3);
|
||||
htmx.findAll(div, ".c2").length.should.equal(2);
|
||||
htmx.findAll(div,".c3").length.should.equal(1);
|
||||
});
|
||||
|
||||
it('should find closest element properly', function () {
|
||||
var div = make("<div><a id='a1'></a><a id='a2'></a></div>");
|
||||
var a = htmx.find(div, "a");
|
||||
htmx.closest(a, "div").should.equal(div);
|
||||
});
|
||||
|
||||
it('should remove element properly', function () {
|
||||
var div = make("<div><a></a></div>");
|
||||
var a = htmx.find(div, "a");
|
||||
htmx.remove(a);
|
||||
div.innerHTML.should.equal("");
|
||||
});
|
||||
|
||||
it('should add class properly', function () {
|
||||
var div = make("<div></div>");
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
htmx.addClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
});
|
||||
|
||||
it('should add class properly after delay', function (done) {
|
||||
var div = make("<div></div>");
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
htmx.addClass(div, "foo", 10);
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
setTimeout(function () {
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
it('should remove class properly', function () {
|
||||
var div = make("<div></div>");
|
||||
htmx.addClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
htmx.removeClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
});
|
||||
|
||||
it('should add class properly after delay', function (done) {
|
||||
var div = make("<div></div>");
|
||||
htmx.addClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
htmx.removeClass(div, "foo", 10);
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
setTimeout(function () {
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
it('should toggle class properly', function () {
|
||||
var div = make("<div></div>");
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
htmx.toggleClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
htmx.toggleClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
});
|
||||
|
||||
it('should take class properly', function () {
|
||||
var div1 = make("<div></div>");
|
||||
var div2 = make("<div></div>");
|
||||
var div3 = make("<div></div>");
|
||||
|
||||
div1.classList.contains("foo").should.equal(false);
|
||||
div2.classList.contains("foo").should.equal(false);
|
||||
div3.classList.contains("foo").should.equal(false);
|
||||
|
||||
htmx.takeClass(div1, "foo");
|
||||
|
||||
div1.classList.contains("foo").should.equal(true);
|
||||
div2.classList.contains("foo").should.equal(false);
|
||||
div3.classList.contains("foo").should.equal(false);
|
||||
|
||||
htmx.takeClass(div2, "foo");
|
||||
|
||||
div1.classList.contains("foo").should.equal(false);
|
||||
div2.classList.contains("foo").should.equal(true);
|
||||
div3.classList.contains("foo").should.equal(false);
|
||||
|
||||
htmx.takeClass(div3, "foo");
|
||||
|
||||
div1.classList.contains("foo").should.equal(false);
|
||||
div2.classList.contains("foo").should.equal(false);
|
||||
div3.classList.contains("foo").should.equal(true);
|
||||
});
|
||||
|
||||
|
||||
})
|
260
www/test/0.1.1/test/core/events.js
Normal file
260
www/test/0.1.1/test/core/events.js
Normal file
@ -0,0 +1,260 @@
|
||||
describe("Core htmx Events", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it("htmx:load fires properly", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("htmx:load", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("GET", "/test", "");
|
||||
this.server.respondWith("GET", "/test", "<div></div>");
|
||||
var div = make("<div hx-get='/test'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("htmx:load", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:configRequest allows attribute addition", function () {
|
||||
var handler = htmx.on("htmx:configRequest", function (evt) {
|
||||
evt.detail.parameters['param'] = "true";
|
||||
});
|
||||
try {
|
||||
var param = null;
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
param = getParameters(xhr)['param'];
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make("<div hx-post='/test'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
param.should.equal("true");
|
||||
} finally {
|
||||
htmx.off("htmx:configRequest", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:configRequest is also dispatched in kebab-case", function () {
|
||||
var handler = htmx.on("htmx:config-request", function (evt) {
|
||||
evt.detail.parameters['param'] = "true";
|
||||
});
|
||||
try {
|
||||
var param = null;
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
param = getParameters(xhr)['param'];
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make("<div hx-post='/test'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
param.should.equal("true");
|
||||
} finally {
|
||||
htmx.off("htmx:config-request", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("events are only dispatched once if kebab and camel case match", function () {
|
||||
var invoked = 0;
|
||||
var handler = htmx.on("custom", function () {
|
||||
invoked = invoked + 1
|
||||
});
|
||||
try {
|
||||
var div = make("<div hx-post='/test'></div>");
|
||||
htmx.trigger(div, "custom");
|
||||
invoked.should.equal(1);
|
||||
} finally {
|
||||
htmx.off("custom", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:configRequest allows attribute removal", function () {
|
||||
var param = "foo";
|
||||
var handler = htmx.on("htmx:configRequest", function (evt) {
|
||||
delete evt.detail.parameters['param'];
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
param = getParameters(xhr)['param'];
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make("<form hx-trigger='click' hx-post='/test'><input name='param' value='foo'></form>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(param, undefined);
|
||||
} finally {
|
||||
htmx.off("htmx:configRequest", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:configRequest allows header tweaking", function () {
|
||||
var header = "foo";
|
||||
var handler = htmx.on("htmx:configRequest", function (evt) {
|
||||
evt.detail.headers['X-My-Header'] = "bar";
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
header = xhr.requestHeaders['X-My-Header'];
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make("<form hx-trigger='click' hx-post='/test'><input name='param' value='foo'></form>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(header, "bar");
|
||||
} finally {
|
||||
htmx.off("htmx:configRequest", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:afterSwap is called when replacing outerHTML", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("htmx:afterSwap", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var div = make("<button hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("htmx:afterSwap", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:afterSettle is called when replacing outerHTML", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("htmx:afterSettle", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var div = make("<button hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("htmx:afterSettle", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:afterRequest is called after a successful request", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("htmx:afterRequest", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make("<button hx-post='/test'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("htmx:afterRequest", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:afterOnLoad is called after a successful request", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("htmx:afterOnLoad", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make("<button hx-post='/test'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("htmx:afterOnLoad", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:afterRequest is called after a failed request", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("htmx:afterRequest", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make("<button hx-post='/test'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("htmx:afterRequest", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:sendError is called after a failed request", function (done) {
|
||||
var called = false;
|
||||
var handler = htmx.on("htmx:sendError", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
this.server.restore(); // turn off server mock so connection doesn't work
|
||||
var div = make("<button hx-post='file://foo'>Foo</button>");
|
||||
div.click();
|
||||
setTimeout(function () {
|
||||
htmx.off("htmx:sendError", handler);
|
||||
should.equal(called, true);
|
||||
done();
|
||||
}, 30);
|
||||
});
|
||||
|
||||
it("htmx:afterRequest is called when replacing outerHTML", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("htmx:afterRequest", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var div = make("<button hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("htmx:afterRequest", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("htmx:afterOnLoad is called when replacing outerHTML", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("htmx:afterOnLoad", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var div = make("<button hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("htmx:afterOnLoad", handler);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
118
www/test/0.1.1/test/core/headers.js
Normal file
118
www/test/0.1.1/test/core/headers.js
Normal file
@ -0,0 +1,118 @@
|
||||
describe("Core htmx AJAX headers", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it("should include the HX-Request header", function(){
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.requestHeaders['HX-Request'].should.be.equal('true');
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
})
|
||||
|
||||
it("should include the HX-Trigger header", function(){
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.requestHeaders['HX-Trigger'].should.equal('d1');
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make('<div id="d1" hx-get="/test"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
})
|
||||
|
||||
it("should include the HX-Trigger-Name header", function(){
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.requestHeaders['HX-Trigger-Name'].should.equal('n1');
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make('<button name="n1" hx-get="/test"></button>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
})
|
||||
|
||||
it("should include the HX-Target header", function(){
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.requestHeaders['HX-Target'].should.equal('d1');
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make('<div hx-target="#d1" hx-get="/test"></div><div id="d1" ></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
})
|
||||
|
||||
it("should handle simple string HX-Trigger response header properly", function(){
|
||||
this.server.respondWith("GET", "/test", [200, {"HX-Trigger" : "foo"}, ""]);
|
||||
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
var invokedEvent = false;
|
||||
div.addEventListener("foo", function (evt) {
|
||||
invokedEvent = true;
|
||||
});
|
||||
div.click();
|
||||
this.server.respond();
|
||||
invokedEvent.should.equal(true);
|
||||
})
|
||||
|
||||
it("should handle basic JSON HX-Trigger response header properly", function(){
|
||||
this.server.respondWith("GET", "/test", [200, {"HX-Trigger" : "{\"foo\":null}"}, ""]);
|
||||
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
var invokedEvent = false;
|
||||
div.addEventListener("foo", function (evt) {
|
||||
invokedEvent = true;
|
||||
should.equal(null, evt.detail.value);
|
||||
evt.detail.elt.should.equal(div);
|
||||
});
|
||||
div.click();
|
||||
this.server.respond();
|
||||
invokedEvent.should.equal(true);
|
||||
})
|
||||
|
||||
it("should handle JSON with array arg HX-Trigger response header properly", function(){
|
||||
this.server.respondWith("GET", "/test", [200, {"HX-Trigger" : "{\"foo\":[1, 2, 3]}"}, ""]);
|
||||
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
var invokedEvent = false;
|
||||
div.addEventListener("foo", function (evt) {
|
||||
invokedEvent = true;
|
||||
evt.detail.elt.should.equal(div);
|
||||
evt.detail.value.should.deep.equal([1, 2, 3]);
|
||||
});
|
||||
div.click();
|
||||
this.server.respond();
|
||||
invokedEvent.should.equal(true);
|
||||
})
|
||||
|
||||
it("should handle JSON with array arg HX-Trigger response header properly", function(){
|
||||
this.server.respondWith("GET", "/test", [200, {"HX-Trigger" : "{\"foo\":{\"a\":1, \"b\":2}}"}, ""]);
|
||||
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
var invokedEvent = false;
|
||||
div.addEventListener("foo", function (evt) {
|
||||
invokedEvent = true;
|
||||
evt.detail.elt.should.equal(div);
|
||||
evt.detail.a.should.equal(1);
|
||||
evt.detail.b.should.equal(2);
|
||||
});
|
||||
div.click();
|
||||
this.server.respond();
|
||||
invokedEvent.should.equal(true);
|
||||
})
|
||||
|
||||
it("should survive malformed JSON in HX-Trigger response header", function(){
|
||||
this.server.respondWith("GET", "/test", [200, {"HX-Trigger" : "{not: valid}"}, ""]);
|
||||
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
})
|
||||
|
||||
});
|
23
www/test/0.1.1/test/core/internals.js
Normal file
23
www/test/0.1.1/test/core/internals.js
Normal file
@ -0,0 +1,23 @@
|
||||
describe("Core htmx internals Tests", function() {
|
||||
|
||||
it("makeFragment works with janky stuff", function(){
|
||||
htmx._("makeFragment")("<html></html>").tagName.should.equal("BODY");
|
||||
htmx._("makeFragment")("<html><body></body></html>").tagName.should.equal("BODY");
|
||||
|
||||
//NB - the tag name should be the *parent* element hosting the HTML since we use the fragment children
|
||||
// for the swap
|
||||
htmx._("makeFragment")("<td></td>").tagName.should.equal("TR");
|
||||
htmx._("makeFragment")("<thead></thead>").tagName.should.equal("TABLE");
|
||||
htmx._("makeFragment")("<col></col>").tagName.should.equal("COLGROUP");
|
||||
htmx._("makeFragment")("<tr></tr>").tagName.should.equal("TBODY");
|
||||
})
|
||||
|
||||
it("set header works with non-ASCII values", function(){
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/dummy");
|
||||
htmx._("safelySetHeaderValue")(xhr, "Example", "привет");
|
||||
// unfortunately I can't test the value :/
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
|
||||
})
|
||||
|
||||
});
|
121
www/test/0.1.1/test/core/parameters.js
Normal file
121
www/test/0.1.1/test/core/parameters.js
Normal file
@ -0,0 +1,121 @@
|
||||
describe("Core htmx Parameter Handling", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('Input includes value', function () {
|
||||
var input = make('<input name="foo" value="bar"/>');
|
||||
var vals = htmx._('getInputValues')(input);
|
||||
vals['foo'].should.equal('bar');
|
||||
})
|
||||
|
||||
it('Input includes value on get', function () {
|
||||
var input = make('<input name="foo" value="bar"/>');
|
||||
var vals = htmx._('getInputValues')(input, "get");
|
||||
vals['foo'].should.equal('bar');
|
||||
})
|
||||
|
||||
it('Input includes form', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var input = byId('i1');
|
||||
var vals = htmx._('getInputValues')(input);
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.equal('rey');
|
||||
})
|
||||
|
||||
it('Input doesnt include form on get', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var input = byId('i1');
|
||||
var vals = htmx._('getInputValues')(input, 'get');
|
||||
vals['foo'].should.equal('bar');
|
||||
should.equal(vals['do'], undefined);
|
||||
})
|
||||
|
||||
it('non-input includes form', function () {
|
||||
var form = make('<form><div id="d1"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = byId('d1');
|
||||
var vals = htmx._('getInputValues')(div, "post");
|
||||
vals['do'].should.equal('rey');
|
||||
})
|
||||
|
||||
it('non-input doesnt include form on get', function () {
|
||||
var form = make('<form><div id="d1"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = byId('d1');
|
||||
var vals = htmx._('getInputValues')(div, "get");
|
||||
should.equal(vals['do'], undefined);
|
||||
})
|
||||
|
||||
it('Basic form works on get', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var vals = htmx._('getInputValues')(form, 'get');
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.equal('rey');
|
||||
})
|
||||
|
||||
it('Basic form works on non-get', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var vals = htmx._('getInputValues')(form, 'post');
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.equal('rey');
|
||||
})
|
||||
|
||||
it('Double values are included as array', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var vals = htmx._('getInputValues')(form);
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.deep.equal(['rey', 'rey']);
|
||||
})
|
||||
|
||||
it('Double values are included as array in correct order', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey1"/><input id="i3" name="do" value="rey2"/></form>');
|
||||
var vals = htmx._('getInputValues')(byId("i3"));
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.deep.equal(['rey1', 'rey2']);
|
||||
})
|
||||
|
||||
it('hx-include works with form', function () {
|
||||
var form = make('<form id="f1"><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = make('<div hx-include="#f1"></div>');
|
||||
var vals = htmx._('getInputValues')(div);
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.deep.equal(['rey', 'rey']);
|
||||
})
|
||||
|
||||
it('hx-include works with input', function () {
|
||||
var form = make('<form id="f1"><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = make('<div hx-include="#i1"></div>');
|
||||
var vals = htmx._('getInputValues')(div);
|
||||
vals['foo'].should.equal('bar');
|
||||
should.equal(vals['do'], undefined);
|
||||
})
|
||||
|
||||
it('hx-include works with two inputs', function () {
|
||||
var form = make('<form id="f1"><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = make('<div hx-include="#i1, #i2"></div>');
|
||||
var vals = htmx._('getInputValues')(div);
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.deep.equal(['rey', 'rey']);
|
||||
})
|
||||
|
||||
it('hx-include works with two inputs, plus form', function () {
|
||||
var form = make('<form id="f1"><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = make('<div hx-include="#i1, #i2, #f1"></div>');
|
||||
var vals = htmx._('getInputValues')(div);
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.deep.equal(['rey', 'rey']);
|
||||
})
|
||||
|
||||
it('correctly URL escapes values', function () {
|
||||
htmx._("urlEncode")({}).should.equal("");
|
||||
htmx._("urlEncode")({"foo": "bar"}).should.equal("foo=bar");
|
||||
htmx._("urlEncode")({"foo": "bar", "do" : "rey"}).should.equal("foo=bar&do=rey");
|
||||
htmx._("urlEncode")({"foo": "bar", "do" : ["rey", "blah"]}).should.equal("foo=bar&do=rey&do=blah");
|
||||
});
|
||||
|
||||
});
|
||||
|
70
www/test/0.1.1/test/core/perf.js
Normal file
70
www/test/0.1.1/test/core/perf.js
Normal file
@ -0,0 +1,70 @@
|
||||
describe("Core htmx perf Tests", function() {
|
||||
|
||||
var HTMX_HISTORY_CACHE_NAME = "htmx-history-cache";
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
|
||||
function stringRepeat(str, num) {
|
||||
num = Number(num);
|
||||
|
||||
var result = '';
|
||||
while (true) {
|
||||
if (num & 1) { // (1)
|
||||
result += str;
|
||||
}
|
||||
num >>>= 1; // (2)
|
||||
if (num <= 0) break;
|
||||
str += str;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
it("DOM processing should be fast", function(){
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
// create an entry with a large content string (256k) and see how fast we can write and read it
|
||||
// to local storage as a single entry
|
||||
var str = stringRepeat("<div>", 30) + stringRepeat("<div><div><span><button hx-get='/test'> Test Get Button </button></span></div></div>\n", 1000) + stringRepeat("</div>", 30);
|
||||
var start = performance.now();
|
||||
var stuff = make(str);
|
||||
var end = performance.now();
|
||||
var timeInMs = end - start;
|
||||
|
||||
// make sure the DOM actually processed
|
||||
var firstBtn = stuff.querySelector("button");
|
||||
firstBtn.click();
|
||||
this.server.respond();
|
||||
firstBtn.innerHTML.should.equal("Clicked!");
|
||||
|
||||
chai.assert(timeInMs < 100, "Should take less than 100ms on most platforms, took: " + timeInMs + "ms");
|
||||
})
|
||||
|
||||
it("history implementation should be fast", function(){
|
||||
// create an entry with a large content string (256k) and see how fast we can write and read it
|
||||
// to local storage as a single entry
|
||||
var entry = {url: stringRepeat("x", 32), content:stringRepeat("x", 256*1024)}
|
||||
var array = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
array.push(entry);
|
||||
}
|
||||
var start = performance.now();
|
||||
var string = JSON.stringify(array);
|
||||
localStorage.setItem(HTMX_HISTORY_CACHE_NAME, string);
|
||||
var reReadString = localStorage.getItem(HTMX_HISTORY_CACHE_NAME);
|
||||
var finalJson = JSON.parse(reReadString);
|
||||
var end = performance.now();
|
||||
var timeInMs = end - start;
|
||||
chai.assert(timeInMs < 300, "Should take less than 300ms on most platforms");
|
||||
})
|
||||
|
||||
})
|
92
www/test/0.1.1/test/core/regressions.js
Normal file
92
www/test/0.1.1/test/core/regressions.js
Normal file
@ -0,0 +1,92 @@
|
||||
describe("Core htmx Regression Tests", function(){
|
||||
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('SVGs process properly in IE11', function()
|
||||
{
|
||||
var btn = make('<svg onclick="document.getElementById(\'contents\').classList.toggle(\'show\')" class="hamburger" viewBox="0 0 100 80" width="25" height="25" style="margin-bottom:-5px">\n' +
|
||||
'<rect width="100" height="20" style="fill:rgb(52, 101, 164)" rx="10"></rect>\n' +
|
||||
'<rect y="30" width="100" height="20" style="fill:rgb(52, 101, 164)" rx="10"></rect>\n' +
|
||||
'<rect y="60" width="100" height="20" style="fill:rgb(52, 101, 164)" rx="10"></rect>\n' +
|
||||
'</svg>')
|
||||
});
|
||||
|
||||
it ('Handles https://github.com/bigskysoftware/htmx/issues/4 properly', function() {
|
||||
this.server.respondWith("GET", "/index2a.php",
|
||||
"<div id='message' hx-swap-oob='true'>I came from message oob swap I should be second</div>" +
|
||||
"<div id='message2' hx-swap-oob='true'>I came from a message2 oob swap I should be third but I am in the wrong spot</div>" +
|
||||
"I'm page2 content (non-swap) I should be first")
|
||||
|
||||
var h1 = make("<h1 hx-get='/index2a.php' hx-target='#page2' hx-trigger='click'>Kutty CLICK ME</h1>" +
|
||||
"<div id='page2' ></div>" +
|
||||
"<div id='message'></div>" +
|
||||
"<div id='message2'></div>")
|
||||
h1.click();
|
||||
this.server.respond();
|
||||
htmx.find("#page2").innerHTML.should.equal("I'm page2 content (non-swap) I should be first")
|
||||
htmx.find("#message").innerHTML.should.equal("I came from message oob swap I should be second")
|
||||
htmx.find("#message2").innerHTML.should.equal("I came from a message2 oob swap I should be third but I am in the wrong spot")
|
||||
});
|
||||
|
||||
it ('Handles https://github.com/bigskysoftware/htmx/issues/33 "empty values" properly', function() {
|
||||
this.server.respondWith("POST", "/htmx.php", function (xhr) {
|
||||
xhr.respond(200, {}, xhr.requestBody);
|
||||
});
|
||||
|
||||
var form = make('<form hx-trigger="click" hx-post="/htmx.php">\n' +
|
||||
'<input type="text" name="variable" value="">\n' +
|
||||
'<button type="submit">Submit</button>\n' +
|
||||
'</form>')
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("variable=")
|
||||
});
|
||||
|
||||
it ('name=id doesnt cause an error', function(){
|
||||
this.server.respondWith("GET", "/test", "Foo<form><input name=\"id\"/></form>")
|
||||
var div = make('<div hx-get="/test">Get It</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.contain("Foo")
|
||||
});
|
||||
|
||||
it ('empty id doesnt cause an error', function(){
|
||||
this.server.respondWith("GET", "/test", "Foo\n<div id=''></div>")
|
||||
var div = make('<div hx-get="/test">Get It</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.contain("Foo")
|
||||
});
|
||||
|
||||
it ('id with dot in value doesnt cause an error', function(){
|
||||
this.server.respondWith("GET", "/test", "Foo <div id='ViewModel.Test'></div>");
|
||||
var div = make('<div hx-get="/test">Get It</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.contain("Foo");
|
||||
});
|
||||
|
||||
it ('@ symbol in attributes does not break requests', function(){
|
||||
this.server.respondWith("GET", "/test", "<div id='d1' @foo='bar'>Foo</div>");
|
||||
var div = make('<div hx-get="/test">Get It</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
byId("d1").getAttribute('@foo').should.equal('bar');
|
||||
});
|
||||
|
||||
it ('@ symbol in attributes does not break attribute swizzling requests', function(){
|
||||
this.server.respondWith("GET", "/test", "<div id='d1' @foo='bar'>Foo</div>");
|
||||
var div = make('<div hx-get="/test"><div id="d1">Foo</div></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
byId("d1").getAttribute('@foo').should.equal('bar');
|
||||
});
|
||||
|
||||
})
|
44
www/test/0.1.1/test/core/verbs.js
Normal file
44
www/test/0.1.1/test/core/verbs.js
Normal file
@ -0,0 +1,44 @@
|
||||
describe("Core htmx AJAX Verbs", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('handles basic posts properly', function () {
|
||||
this.server.respondWith("POST", "/test", "post");
|
||||
var div = make('<div hx-post="/test">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("post");
|
||||
})
|
||||
|
||||
it('handles basic put properly', function () {
|
||||
this.server.respondWith("PUT", "/test", "put");
|
||||
var div = make('<div hx-put="/test">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("put");
|
||||
})
|
||||
|
||||
it('handles basic patch properly', function () {
|
||||
this.server.respondWith("PATCH", "/test", "patch");
|
||||
var div = make('<div hx-patch="/test">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("patch");
|
||||
})
|
||||
|
||||
it('handles basic delete properly', function () {
|
||||
this.server.respondWith("DELETE", "/test", "delete");
|
||||
var div = make('<div hx-delete="/test">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("delete");
|
||||
})
|
||||
|
||||
});
|
||||
|
21
www/test/0.1.1/test/ext/ajax-header.js
Normal file
21
www/test/0.1.1/test/ext/ajax-header.js
Normal file
@ -0,0 +1,21 @@
|
||||
describe("ajax-header extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('Sends the X-Requested-With header', function () {
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, xhr.requestHeaders['X-Requested-With'])
|
||||
});
|
||||
var btn = make('<button hx-get="/test" hx-ext="ajax-header">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("XMLHttpRequest");
|
||||
});
|
||||
|
||||
});
|
27
www/test/0.1.1/test/ext/bad-extension.js
Normal file
27
www/test/0.1.1/test/ext/bad-extension.js
Normal file
@ -0,0 +1,27 @@
|
||||
describe("bad extension", function() {
|
||||
htmx.defineExtension("bad-extension", {
|
||||
onEvent : function(name, evt) {throw "onEvent"},
|
||||
transformResponse : function(text, xhr, elt) {throw "transformRequest"},
|
||||
isInlineSwap : function(swapStyle) {throw "isInlineSwap"},
|
||||
handleSwap : function(swapStyle, target, fragment, settleInfo) {throw "handleSwap"},
|
||||
encodeParameters : function(xhr, parameters, elt) {throw "encodeParmeters"}
|
||||
}
|
||||
)
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('does not blow up rendering', function () {
|
||||
this.server.respondWith("GET", "/test", "clicked!");
|
||||
var div = make('<div hx-get="/test" hx-ext="bad-extension">Click Me!</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("clicked!");
|
||||
});
|
||||
|
||||
});
|
55
www/test/0.1.1/test/ext/class-tools.js
Normal file
55
www/test/0.1.1/test/ext/class-tools.js
Normal file
@ -0,0 +1,55 @@
|
||||
describe("class-tools extension", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('adds classes properly', function(done)
|
||||
{
|
||||
var div = make('<div hx-ext="class-tools" classes="add c1">Click Me!</div>')
|
||||
should.equal(div.classList.length, 0);
|
||||
setTimeout(function(){
|
||||
should.equal(div.classList.contains("c1"), true);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('removes classes properly', function(done)
|
||||
{
|
||||
var div = make('<div class="foo bar" hx-ext="class-tools" classes="remove bar">Click Me!</div>')
|
||||
should.equal(div.classList.contains("foo"), true);
|
||||
should.equal(div.classList.contains("bar"), true);
|
||||
setTimeout(function(){
|
||||
should.equal(div.classList.contains("foo"), true);
|
||||
should.equal(div.classList.contains("bar"), false);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('adds classes properly w/ data-* prefix', function(done)
|
||||
{
|
||||
var div = make('<div hx-ext="class-tools" data-classes="add c1">Click Me!</div>')
|
||||
should.equal(div.classList.length, 0);
|
||||
setTimeout(function(){
|
||||
should.equal(div.classList.contains("c1"), true);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('extension can be on parent', function(done)
|
||||
{
|
||||
var div = make('<div hx-ext="class-tools"><div id="d1" classes="add c1">Click Me!</div></div>')
|
||||
should.equal(div.classList.length, 0);
|
||||
setTimeout(function(){
|
||||
should.equal(div.classList.contains("c1"), false);
|
||||
should.equal(byId("d1").classList.contains("c1"), true);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
|
||||
})
|
30
www/test/0.1.1/test/ext/client-side-templates.js
Normal file
30
www/test/0.1.1/test/ext/client-side-templates.js
Normal file
@ -0,0 +1,30 @@
|
||||
describe("client-side-templates extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('works on basic mustache template', function () {
|
||||
this.server.respondWith("GET", "/test", '{"foo":"bar"}');
|
||||
var btn = make('<button hx-get="/test" hx-ext="client-side-templates" mustache-template="mt1">Click Me!</button>')
|
||||
make('<script id="mt1" type="x-tmpl-mustache">*{{foo}}*</script>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("*bar*");
|
||||
});
|
||||
|
||||
it('works on basic handlebars template', function () {
|
||||
this.server.respondWith("GET", "/test", '{"foo":"bar"}');
|
||||
var btn = make('<button hx-get="/test" hx-ext="client-side-templates" handlebars-template="hb1">Click Me!</button>')
|
||||
Handlebars.partials["hb1"] = Handlebars.compile("*{{foo}}*");
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("*bar*");
|
||||
});
|
||||
|
||||
|
||||
});
|
19
www/test/0.1.1/test/ext/debug.js
Normal file
19
www/test/0.1.1/test/ext/debug.js
Normal file
@ -0,0 +1,19 @@
|
||||
describe("debug extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('works on basic request', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="debug">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
});
|
60
www/test/0.1.1/test/ext/extension-swap.js
Normal file
60
www/test/0.1.1/test/ext/extension-swap.js
Normal file
@ -0,0 +1,60 @@
|
||||
describe("default extensions behavior", function() {
|
||||
|
||||
var loadCalls, afterSwapCalls, afterSettleCalls;
|
||||
|
||||
beforeEach(function () {
|
||||
loadCalls = [];
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
|
||||
htmx.defineExtension("ext-testswap", {
|
||||
onEvent : function(name, evt) {
|
||||
if (name === "htmx:load") {
|
||||
loadCalls.push(evt.detail.elt);
|
||||
}
|
||||
},
|
||||
handleSwap: function (swapStyle, target, fragment, settleInfo) {
|
||||
// simple outerHTML replacement for tests
|
||||
var parentEl = target.parentElement;
|
||||
parentEl.removeChild(target);
|
||||
return [parentEl.appendChild(fragment)]; // return the newly added element
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
htmx.removeExtension("ext-testswap");
|
||||
});
|
||||
|
||||
it('handleSwap: afterSwap and afterSettle triggered if extension defined on parent', function () {
|
||||
this.server.respondWith("GET", "/test", '<button>Clicked!</button>');
|
||||
var div = make('<div hx-ext="ext-testswap"><button hx-get="/test" hx-swap="testswap">Click Me!</button></div>');
|
||||
var btn = div.firstChild;
|
||||
btn.click()
|
||||
this.server.respond();
|
||||
loadCalls.length.should.equal(1);
|
||||
loadCalls[0].textContent.should.equal('Clicked!'); // the new button is loaded
|
||||
});
|
||||
|
||||
it('handleSwap: new content is handled by htmx', function() {
|
||||
this.server.respondWith("GET", "/test", '<button id="test-ext-testswap">Clicked!<span hx-get="/test-inner" hx-trigger="load"></span></button>');
|
||||
this.server.respondWith("GET", "/test-inner", 'Loaded!');
|
||||
make('<div hx-ext="ext-testswap"><button hx-get="/test" hx-swap="testswap">Click Me!</button></div>').querySelector('button').click();
|
||||
|
||||
this.server.respond(); // call /test via button trigger=click
|
||||
var btn = byId('test-ext-testswap');
|
||||
btn.textContent.should.equal('Clicked!');
|
||||
loadCalls.length.should.equal(1);
|
||||
loadCalls[0].textContent.should.equal('Clicked!'); // the new button is loaded
|
||||
|
||||
this.server.respond(); // call /test-inner via span trigger=load
|
||||
btn.textContent.should.equal("Clicked!Loaded!");
|
||||
loadCalls.length.should.equal(2);
|
||||
loadCalls[1].textContent.should.equal('Loaded!'); // the new span is loaded
|
||||
});
|
||||
|
||||
});
|
50
www/test/0.1.1/test/ext/hyperscript.js
Normal file
50
www/test/0.1.1/test/ext/hyperscript.js
Normal file
@ -0,0 +1,50 @@
|
||||
describe("hyperscript integration", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('can trigger with a custom event', function () {
|
||||
this.server.respondWith("GET", "/test", "Custom Event Sent!");
|
||||
var btn = make('<button _="on click send customEvent" hx-trigger="customEvent" hx-get="/test">Click Me!</button>')
|
||||
htmx.trigger(btn, "htmx:load"); // have to manually trigger the load event for non-AJAX dynamic content
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Custom Event Sent!");
|
||||
});
|
||||
|
||||
it('can handle htmx driven events', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button _="on htmx:afterSettle add .afterSettle" hx-get="/test">Click Me!</button>')
|
||||
htmx.trigger(btn, "htmx:load");
|
||||
btn.classList.contains("afterSettle").should.equal(false);
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.classList.contains("afterSettle").should.equal(true);
|
||||
});
|
||||
|
||||
it('can handle htmx error events', function () {
|
||||
this.server.respondWith("GET", "/test", [404, {}, "Bad request"]);
|
||||
var div = make('<div id="d1"></div>')
|
||||
var btn = make('<button _="on htmx:error(errorInfo) put errorInfo.error into #d1.innerHTML" hx-get="/test">Click Me!</button>')
|
||||
htmx.trigger(btn, "htmx:load");
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Response Status Error Code 404 from /test");
|
||||
});
|
||||
|
||||
it('hyperscript in non-htmx annotated nodes is evaluated', function () {
|
||||
this.server.respondWith("GET", "/test", "<div><div><div id='d1' _='on click put \"Clicked...\" into my.innerHTML'></div></div></div>");
|
||||
var btn = make('<button hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
var newDiv = byId("d1");
|
||||
newDiv.click();
|
||||
newDiv.innerText.should.equal("Clicked...");
|
||||
});
|
||||
|
||||
});
|
23
www/test/0.1.1/test/ext/include-vals.js
Normal file
23
www/test/0.1.1/test/ext/include-vals.js
Normal file
@ -0,0 +1,23 @@
|
||||
describe("include-vals extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('Includes values properly', function () {
|
||||
var params = {};
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
params = getParameters(xhr);
|
||||
xhr.respond(200, {}, "clicked");
|
||||
});
|
||||
var btn = make('<button hx-post="/test" hx-ext="include-vals" include-vals="foo:\'bar\'">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
params['foo'].should.equal("bar");
|
||||
});
|
||||
|
||||
});
|
136
www/test/0.1.1/test/ext/json-enc.js
Normal file
136
www/test/0.1.1/test/ext/json-enc.js
Normal file
@ -0,0 +1,136 @@
|
||||
//
|
||||
describe("json-enc extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('handles basic post properly', function () {
|
||||
var jsonResponseBody = JSON.stringify({});
|
||||
this.server.respondWith("POST", "/test", jsonResponseBody);
|
||||
var div = make("<div hx-post='/test' hx-ext='json-enc'>click me</div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
this.server.lastRequest.response.should.equal("{}");
|
||||
})
|
||||
|
||||
it('handles basic put properly', function () {
|
||||
var jsonResponseBody = JSON.stringify({});
|
||||
this.server.respondWith("PUT", "/test", jsonResponseBody);
|
||||
var div = make('<div hx-put="/test" hx-ext="json-enc">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
this.server.lastRequest.response.should.equal("{}");
|
||||
})
|
||||
|
||||
it('handles basic patch properly', function () {
|
||||
var jsonResponseBody = JSON.stringify({});
|
||||
this.server.respondWith("PATCH", "/test", jsonResponseBody);
|
||||
var div = make('<div hx-patch="/test" hx-ext="json-enc">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
this.server.lastRequest.response.should.equal("{}");
|
||||
})
|
||||
|
||||
it('handles basic delete properly', function () {
|
||||
var jsonResponseBody = JSON.stringify({});
|
||||
this.server.respondWith("DELETE", "/test", jsonResponseBody);
|
||||
var div = make('<div hx-delete="/test" hx-ext="json-enc">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
this.server.lastRequest.response.should.equal("{}");
|
||||
})
|
||||
|
||||
it('handles post with form parameters', function () {
|
||||
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
var values = JSON.parse(xhr.requestBody);
|
||||
values.should.have.keys("username","password");
|
||||
values["username"].should.be.equal("joe");
|
||||
values["password"].should.be.equal("123456");
|
||||
var ans = { "passwordok": values["password"] == "123456"};
|
||||
xhr.respond(200, {}, JSON.stringify(ans));
|
||||
});
|
||||
|
||||
var html = make('<form hx-post="/test" hx-ext="json-enc" > ' +
|
||||
'<input type="text" name="username" value="joe"> ' +
|
||||
'<input type="password" name="password" value="123456"> ' +
|
||||
'<button id="btnSubmit">Submit</button> ');
|
||||
|
||||
byId("btnSubmit").click();
|
||||
this.server.respond();
|
||||
this.server.lastRequest.response.should.equal('{"passwordok":true}');
|
||||
})
|
||||
|
||||
it('handles put with form parameters', function () {
|
||||
|
||||
this.server.respondWith("PUT", "/test", function (xhr) {
|
||||
var values = JSON.parse(xhr.requestBody);
|
||||
values.should.have.keys("username","password");
|
||||
values["username"].should.be.equal("joe");
|
||||
values["password"].should.be.equal("123456");
|
||||
var ans = { "passwordok": values["password"] == "123456"};
|
||||
xhr.respond(200, {}, JSON.stringify(ans));
|
||||
});
|
||||
|
||||
var html = make('<form hx-put="/test" hx-ext="json-enc" > ' +
|
||||
'<input type="text" name="username" value="joe"> ' +
|
||||
'<input type="password" name="password" value="123456"> ' +
|
||||
'<button id="btnSubmit">Submit</button> ');
|
||||
|
||||
byId("btnSubmit").click();
|
||||
this.server.respond();
|
||||
this.server.lastRequest.response.should.equal('{"passwordok":true}');
|
||||
})
|
||||
|
||||
|
||||
it('handles patch with form parameters', function () {
|
||||
|
||||
this.server.respondWith("PATCH", "/test", function (xhr) {
|
||||
var values = JSON.parse(xhr.requestBody);
|
||||
values.should.have.keys("username","password");
|
||||
values["username"].should.be.equal("joe");
|
||||
values["password"].should.be.equal("123456");
|
||||
var ans = { "passwordok": values["password"] == "123456"};
|
||||
xhr.respond(200, {}, JSON.stringify(ans));
|
||||
});
|
||||
|
||||
var html = make('<form hx-patch="/test" hx-ext="json-enc" > ' +
|
||||
'<input type="text" name="username" value="joe"> ' +
|
||||
'<input type="password" name="password" value="123456"> ' +
|
||||
'<button id="btnSubmit">Submit</button> ');
|
||||
|
||||
byId("btnSubmit").click();
|
||||
this.server.respond();
|
||||
this.server.lastRequest.response.should.equal('{"passwordok":true}');
|
||||
})
|
||||
|
||||
it('handles delete with form parameters', function () {
|
||||
|
||||
this.server.respondWith("DELETE", "/test", function (xhr) {
|
||||
var values = JSON.parse(xhr.requestBody);
|
||||
values.should.have.keys("username","password");
|
||||
values["username"].should.be.equal("joe");
|
||||
values["password"].should.be.equal("123456");
|
||||
var ans = { "passwordok": values["password"] == "123456"};
|
||||
xhr.respond(200, {}, JSON.stringify(ans));
|
||||
});
|
||||
|
||||
var html = make('<form hx-delete="/test" hx-ext="json-enc" > ' +
|
||||
'<input type="text" name="username" value="joe"> ' +
|
||||
'<input type="password" name="password" value="123456"> ' +
|
||||
'<button id="btnSubmit">Submit</button> ');
|
||||
|
||||
byId("btnSubmit").click();
|
||||
this.server.respond();
|
||||
this.server.lastRequest.response.should.equal('{"passwordok":true}');
|
||||
})
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
53
www/test/0.1.1/test/ext/method-override.js
Normal file
53
www/test/0.1.1/test/ext/method-override.js
Normal file
@ -0,0 +1,53 @@
|
||||
describe("method-override extension", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a DELETE request with proper headers', function()
|
||||
{
|
||||
this.server.respondWith("DELETE", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('DELETE');
|
||||
xhr.method.should.equal("POST")
|
||||
xhr.respond(200, {}, "Deleted!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-ext="method-override" hx-delete="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Deleted!");
|
||||
});
|
||||
|
||||
it('issues a PATCH request with proper headers', function()
|
||||
{
|
||||
this.server.respondWith("PATCH", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('PATCH');
|
||||
xhr.method.should.equal("POST")
|
||||
xhr.respond(200, {}, "Patched!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-ext="method-override" hx-patch="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Patched!");
|
||||
});
|
||||
|
||||
it('issues a PUT request with proper headers', function()
|
||||
{
|
||||
this.server.respondWith("PUT", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('PUT');
|
||||
xhr.method.should.equal("POST")
|
||||
xhr.respond(200, {}, "Putted!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-ext="method-override" hx-put="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Putted!");
|
||||
});
|
||||
|
||||
})
|
31
www/test/0.1.1/test/ext/morphdom-swap.js
Normal file
31
www/test/0.1.1/test/ext/morphdom-swap.js
Normal file
@ -0,0 +1,31 @@
|
||||
describe("morphdom-swap extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('works on basic request', function () {
|
||||
this.server.respondWith("GET", "/test", "<button>Clicked!</button>!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="morphdom-swap" hx-swap="morphdom" >Click Me!</button>')
|
||||
btn.click();
|
||||
should.equal(btn.getAttribute("hx-get"), "/test");
|
||||
this.server.respond();
|
||||
should.equal(btn.getAttribute("hx-get"), null);
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with htmx elements in new content', function () {
|
||||
this.server.respondWith("GET", "/test", '<button>Clicked!<span hx-get="/test-inner" hx-trigger="load" hx-swap="morphdom"></span></button>');
|
||||
this.server.respondWith("GET", "/test-inner", 'Loaded!');
|
||||
var btn = make('<div hx-ext="morphdom-swap"><button hx-get="/test" hx-swap="morphdom">Click Me!</button></div>').querySelector('button');
|
||||
btn.click();
|
||||
this.server.respond(); // call /test via button trigger=click
|
||||
this.server.respond(); // call /test-inner via span trigger=load
|
||||
btn.innerHTML.should.equal("Clicked!Loaded!");
|
||||
});
|
||||
|
||||
});
|
142
www/test/0.1.1/test/ext/path-deps.js
Normal file
142
www/test/0.1.1/test/ext/path-deps.js
Normal file
@ -0,0 +1,142 @@
|
||||
describe("path-deps extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('path-deps basic case works', function () {
|
||||
this.server.respondWith("POST", "/test", "Clicked!");
|
||||
this.server.respondWith("GET", "/test2", "Deps fired!");
|
||||
var btn = make('<button hx-post="/test" hx-ext="path-deps">Click Me!</button>')
|
||||
var div = make('<div hx-get="/test2" hx-trigger="path-deps" path-deps="/test">FOO</div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
div.innerHTML.should.equal("FOO");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Deps fired!");
|
||||
});
|
||||
|
||||
it('path-deps works with trailing slash', function () {
|
||||
this.server.respondWith("POST", "/test", "Clicked!");
|
||||
this.server.respondWith("GET", "/test2", "Deps fired!");
|
||||
var btn = make('<button hx-post="/test" hx-ext="path-deps">Click Me!</button>')
|
||||
var div = make('<div hx-get="/test2" hx-trigger="path-deps" path-deps="/test/">FOO</div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
div.innerHTML.should.equal("FOO");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Deps fired!");
|
||||
});
|
||||
|
||||
it('path-deps GET does not trigger', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
this.server.respondWith("GET", "/test2", "Deps fired!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="path-deps">Click Me!</button>')
|
||||
var div = make('<div hx-get="/test2" hx-trigger="path-deps" path-deps="/test">FOO</div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
div.innerHTML.should.equal("FOO");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("FOO");
|
||||
});
|
||||
|
||||
it('path-deps dont trigger on path mismatch', function () {
|
||||
this.server.respondWith("POST", "/test", "Clicked!");
|
||||
this.server.respondWith("GET", "/test2", "Deps fired!");
|
||||
var btn = make('<button hx-post="/test" hx-ext="path-deps">Click Me!</button>')
|
||||
var div = make('<div hx-get="/test2" hx-trigger="path-deps" path-deps="/test2">FOO</div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
div.innerHTML.should.equal("FOO");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("FOO");
|
||||
});
|
||||
|
||||
it('path-deps dont trigger on path longer than request', function () {
|
||||
this.server.respondWith("POST", "/test", "Clicked!");
|
||||
this.server.respondWith("GET", "/test2", "Deps fired!");
|
||||
var btn = make('<button hx-post="/test" hx-ext="path-deps">Click Me!</button>')
|
||||
var div = make('<div hx-get="/test2" hx-trigger="path-deps" path-deps="/test/child">FOO</div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
div.innerHTML.should.equal("FOO");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("FOO");
|
||||
});
|
||||
|
||||
it('path-deps trigger on path shorter than request', function () {
|
||||
this.server.respondWith("POST", "/test/child", "Clicked!");
|
||||
this.server.respondWith("GET", "/test2", "Deps fired!");
|
||||
var btn = make('<button hx-post="/test/child" hx-ext="path-deps">Click Me!</button>')
|
||||
var div = make('<div hx-get="/test2" hx-trigger="path-deps" path-deps="/test">FOO</div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
div.innerHTML.should.equal("FOO");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Deps fired!");
|
||||
});
|
||||
|
||||
it('path-deps trigger on *-at-start path', function () {
|
||||
this.server.respondWith("POST", "/test/child/test", "Clicked!");
|
||||
this.server.respondWith("GET", "/test2", "Deps fired!");
|
||||
var btn = make('<button hx-post="/test/child/test" hx-ext="path-deps">Click Me!</button>')
|
||||
var div = make('<div hx-get="/test2" hx-trigger="path-deps" path-deps="/*/child/test">FOO</div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
div.innerHTML.should.equal("FOO");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Deps fired!");
|
||||
});
|
||||
|
||||
it('path-deps trigger on *-in-middle path', function () {
|
||||
this.server.respondWith("POST", "/test/child/test", "Clicked!");
|
||||
this.server.respondWith("GET", "/test2", "Deps fired!");
|
||||
var btn = make('<button hx-post="/test/child/test" hx-ext="path-deps">Click Me!</button>')
|
||||
var div = make('<div hx-get="/test2" hx-trigger="path-deps" path-deps="/test/*/test">FOO</div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
div.innerHTML.should.equal("FOO");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Deps fired!");
|
||||
});
|
||||
|
||||
it('path-deps trigger on *-at-end path', function () {
|
||||
this.server.respondWith("POST", "/test/child/test", "Clicked!");
|
||||
this.server.respondWith("GET", "/test2", "Deps fired!");
|
||||
var btn = make('<button hx-post="/test/child/test" hx-ext="path-deps">Click Me!</button>')
|
||||
var div = make('<div hx-get="/test2" hx-trigger="path-deps" path-deps="/test/child/*">FOO</div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
div.innerHTML.should.equal("FOO");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Deps fired!");
|
||||
});
|
||||
|
||||
it('path-deps trigger all *s path', function () {
|
||||
this.server.respondWith("POST", "/test/child/test", "Clicked!");
|
||||
this.server.respondWith("GET", "/test2", "Deps fired!");
|
||||
var btn = make('<button hx-post="/test/child/test" hx-ext="path-deps">Click Me!</button>')
|
||||
var div = make('<div hx-get="/test2" hx-trigger="path-deps" path-deps="/*/*/*">FOO</div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
div.innerHTML.should.equal("FOO");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Deps fired!");
|
||||
});
|
||||
|
||||
|
||||
});
|
43
www/test/0.1.1/test/ext/remove-me.js
Normal file
43
www/test/0.1.1/test/ext/remove-me.js
Normal file
@ -0,0 +1,43 @@
|
||||
describe("remove-me extension", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('removes elements properly', function(done)
|
||||
{
|
||||
var div = make('<div id="d1" hx-ext="remove-me" remove-me="20ms">Click Me!</div>')
|
||||
byId("d1").should.equal(div)
|
||||
setTimeout(function(){
|
||||
should.equal(byId("d1"), null);
|
||||
done();
|
||||
}, 40);
|
||||
});
|
||||
|
||||
|
||||
it('removes properly w/ data-* prefix', function(done)
|
||||
{
|
||||
var div = make('<div hx-ext="remove-me" data-remove-me="20ms">Click Me!</div>')
|
||||
should.equal(div.classList.length, 0);
|
||||
setTimeout(function(){
|
||||
should.equal(div.parentElement, null);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('extension can be on parent', function(done)
|
||||
{
|
||||
var div = make('<div hx-ext="remove-me"><div id="d1" remove-me="20ms">Click Me!</div></div>')
|
||||
should.equal(div.classList.length, 0);
|
||||
setTimeout(function(){
|
||||
should.equal(byId("d1"), null);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
|
||||
})
|
52
www/test/0.1.1/test/img/bars.svg
Normal file
52
www/test/0.1.1/test/img/bars.svg
Normal file
@ -0,0 +1,52 @@
|
||||
<svg width="16" height="16" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill="#494949">
|
||||
<rect y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.5s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.5s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="30" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.25s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.25s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="60" width="15" height="140" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="90" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.25s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.25s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="120" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.5s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.5s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
156
www/test/0.1.1/test/index.html
Normal file
156
www/test/0.1.1/test/index.html
Normal file
@ -0,0 +1,156 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Mocha Tests</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
|
||||
<meta http-equiv="cache-control" content="no-cache, must-revalidate, post-check=0, pre-check=0" />
|
||||
<meta http-equiv="cache-control" content="max-age=0" />
|
||||
<meta http-equiv="expires" content="0" />
|
||||
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
|
||||
<meta http-equiv="pragma" content="no-cache" />
|
||||
<meta name="htmx-config" content='{"historyEnabled":false,"defaultSettleDelay":0}'>
|
||||
</head>
|
||||
<body style="padding:20px;font-family: sans-serif">
|
||||
|
||||
<h1 style="margin-top: 40px">htmx.js test suite</h1>
|
||||
|
||||
<h2>Scratch Page</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="scratch.html">Scratch Page</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Manual Tests</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="manual/browser-only-tests.html">Core Browser-Only Tests</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="manual/yes-indicator-css.html">Include Indicator CSS Test</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="manual/no-indicator-css.html">Exclude Indicator CSS Test</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="manual/confirm-and-prompt.html">Confirm & Prompt Test</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="manual/scroll-test-startEnd.html">Scroll Test 1 - Start/End</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="manual/scroll-test-eventHandler.html">Scroll Test 2 - Event Handler</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="manual/sse.html">SSE - Multiple Event Sources - Single Event Name</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="manual/sse-multichannel.html">SSE - Single Event Source - Multiple Event Names</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="manual/sse-settle.html">SSE - Settling</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Mocha Test Suite</h2>
|
||||
|
||||
<script src="../node_modules/chai/chai.js"></script>
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="../src/htmx.js"></script>
|
||||
<script class="mocha-init">
|
||||
mocha.setup('bdd');
|
||||
mocha.checkLeaks();
|
||||
should = chai.should();
|
||||
</script>
|
||||
|
||||
<script src="util/util.js"></script>
|
||||
|
||||
<!-- core tests -->
|
||||
<script src="core/internals.js"></script>
|
||||
<script src="core/api.js"></script>
|
||||
<script src="core/ajax.js"></script>
|
||||
<script src="core/verbs.js"></script>
|
||||
<script src="core/parameters.js"></script>
|
||||
<script src="core/headers.js"></script>
|
||||
<script src="core/regressions.js"></script>
|
||||
<script src="core/perf.js"></script>
|
||||
|
||||
<!-- attribute tests -->
|
||||
<script src="attributes/hx-boost.js"></script>
|
||||
<script src="attributes/hx-delete.js"></script>
|
||||
<script src="attributes/hx-ext.js"></script>
|
||||
<script src="attributes/hx-get.js"></script>
|
||||
<script src="attributes/hx-include.js"></script>
|
||||
<script src="attributes/hx-indicator.js"></script>
|
||||
<script src="attributes/hx-params.js"></script>
|
||||
<script src="attributes/hx-patch.js"></script>
|
||||
<script src="attributes/hx-post.js"></script>
|
||||
<script src="attributes/hx-push-url.js"></script>
|
||||
<script src="attributes/hx-put.js"></script>
|
||||
<script src="attributes/hx-select.js"></script>
|
||||
<script src="attributes/hx-sse.js"></script>
|
||||
<script src="attributes/hx-swap-oob.js"></script>
|
||||
<script src="attributes/hx-swap.js"></script>
|
||||
<script src="attributes/hx-target.js"></script>
|
||||
<script src="attributes/hx-trigger.js"></script>
|
||||
<script src="attributes/hx-vars.js"></script>
|
||||
<script src="attributes/hx-ws.js"></script>
|
||||
|
||||
<!-- hyperscript integration -->
|
||||
<script src="lib/_hyperscript.js"></script>
|
||||
<script src="ext/hyperscript.js"></script>
|
||||
|
||||
<!-- extension tests -->
|
||||
<script src="ext/extension-swap.js"></script>
|
||||
|
||||
<script src="../src/ext/method-override.js"></script>
|
||||
<script src="ext/method-override.js"></script>
|
||||
|
||||
<script src="../src/ext/debug.js"></script>
|
||||
<script src="ext/debug.js"></script>
|
||||
|
||||
<script src="lib/morphdom-umd.js"></script>
|
||||
<script src="../src/ext/morphdom-swap.js"></script>
|
||||
<script src="ext/morphdom-swap.js"></script>
|
||||
|
||||
<script src="../src/ext/json-enc.js"></script>
|
||||
<script src="ext/json-enc.js"></script>
|
||||
|
||||
<script src="lib/handlebars-v4.7.6.js"></script>
|
||||
<script src="lib/mustache.js"></script>
|
||||
<script src="../src/ext/client-side-templates.js"></script>
|
||||
<script src="ext/client-side-templates.js"></script>
|
||||
|
||||
<script src="../src/ext/path-deps.js"></script>
|
||||
<script src="ext/path-deps.js"></script>
|
||||
|
||||
<script src="../src/ext/class-tools.js"></script>
|
||||
<script src="ext/class-tools.js"></script>
|
||||
|
||||
<script src="ext/bad-extension.js"></script>
|
||||
|
||||
<script src="../src/ext/remove-me.js"></script>
|
||||
<script src="ext/remove-me.js"></script>
|
||||
|
||||
<script src="../src/ext/include-vals.js"></script>
|
||||
<script src="ext/include-vals.js"></script>
|
||||
|
||||
<script src="../src/ext/ajax-header.js"></script>
|
||||
<script src="ext/ajax-header.js"></script>
|
||||
|
||||
<!-- events last so they don't screw up other tests -->
|
||||
<script src="core/events.js"></script>
|
||||
|
||||
<div id="mocha"></div>
|
||||
|
||||
<script class="mocha-exec">
|
||||
mocha.run();
|
||||
</script>
|
||||
<em>Work Area</em>
|
||||
<hr/>
|
||||
<div id="work-area" hx-history-elt>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
1779
www/test/0.1.1/test/lib/_hyperscript.js
Normal file
1779
www/test/0.1.1/test/lib/_hyperscript.js
Normal file
File diff suppressed because it is too large
Load Diff
5210
www/test/0.1.1/test/lib/handlebars-v4.7.6.js
Normal file
5210
www/test/0.1.1/test/lib/handlebars-v4.7.6.js
Normal file
File diff suppressed because one or more lines are too long
763
www/test/0.1.1/test/lib/morphdom-umd.js
Normal file
763
www/test/0.1.1/test/lib/morphdom-umd.js
Normal file
@ -0,0 +1,763 @@
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global = global || self, global.morphdom = factory());
|
||||
}(this, function () { 'use strict';
|
||||
|
||||
var DOCUMENT_FRAGMENT_NODE = 11;
|
||||
|
||||
function morphAttrs(fromNode, toNode) {
|
||||
var toNodeAttrs = toNode.attributes;
|
||||
var attr;
|
||||
var attrName;
|
||||
var attrNamespaceURI;
|
||||
var attrValue;
|
||||
var fromValue;
|
||||
|
||||
// document-fragments dont have attributes so lets not do anything
|
||||
if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update attributes on original DOM element
|
||||
for (var i = toNodeAttrs.length - 1; i >= 0; i--) {
|
||||
attr = toNodeAttrs[i];
|
||||
attrName = attr.name;
|
||||
attrNamespaceURI = attr.namespaceURI;
|
||||
attrValue = attr.value;
|
||||
|
||||
if (attrNamespaceURI) {
|
||||
attrName = attr.localName || attrName;
|
||||
fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);
|
||||
|
||||
if (fromValue !== attrValue) {
|
||||
if (attr.prefix === 'xmlns'){
|
||||
attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix
|
||||
}
|
||||
fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);
|
||||
}
|
||||
} else {
|
||||
fromValue = fromNode.getAttribute(attrName);
|
||||
|
||||
if (fromValue !== attrValue) {
|
||||
fromNode.setAttribute(attrName, attrValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any extra attributes found on the original DOM element that
|
||||
// weren't found on the target element.
|
||||
var fromNodeAttrs = fromNode.attributes;
|
||||
|
||||
for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {
|
||||
attr = fromNodeAttrs[d];
|
||||
attrName = attr.name;
|
||||
attrNamespaceURI = attr.namespaceURI;
|
||||
|
||||
if (attrNamespaceURI) {
|
||||
attrName = attr.localName || attrName;
|
||||
|
||||
if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {
|
||||
fromNode.removeAttributeNS(attrNamespaceURI, attrName);
|
||||
}
|
||||
} else {
|
||||
if (!toNode.hasAttribute(attrName)) {
|
||||
fromNode.removeAttribute(attrName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var range; // Create a range object for efficently rendering strings to elements.
|
||||
var NS_XHTML = 'http://www.w3.org/1999/xhtml';
|
||||
|
||||
var doc = typeof document === 'undefined' ? undefined : document;
|
||||
var HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');
|
||||
var HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();
|
||||
|
||||
function createFragmentFromTemplate(str) {
|
||||
var template = doc.createElement('template');
|
||||
template.innerHTML = str;
|
||||
return template.content.childNodes[0];
|
||||
}
|
||||
|
||||
function createFragmentFromRange(str) {
|
||||
if (!range) {
|
||||
range = doc.createRange();
|
||||
range.selectNode(doc.body);
|
||||
}
|
||||
|
||||
var fragment = range.createContextualFragment(str);
|
||||
return fragment.childNodes[0];
|
||||
}
|
||||
|
||||
function createFragmentFromWrap(str) {
|
||||
var fragment = doc.createElement('body');
|
||||
fragment.innerHTML = str;
|
||||
return fragment.childNodes[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* This is about the same
|
||||
* var html = new DOMParser().parseFromString(str, 'text/html');
|
||||
* return html.body.firstChild;
|
||||
*
|
||||
* @method toElement
|
||||
* @param {String} str
|
||||
*/
|
||||
function toElement(str) {
|
||||
str = str.trim();
|
||||
if (HAS_TEMPLATE_SUPPORT) {
|
||||
// avoid restrictions on content for things like `<tr><th>Hi</th></tr>` which
|
||||
// createContextualFragment doesn't support
|
||||
// <template> support not available in IE
|
||||
return createFragmentFromTemplate(str);
|
||||
} else if (HAS_RANGE_SUPPORT) {
|
||||
return createFragmentFromRange(str);
|
||||
}
|
||||
|
||||
return createFragmentFromWrap(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if two node's names are the same.
|
||||
*
|
||||
* NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same
|
||||
* nodeName and different namespace URIs.
|
||||
*
|
||||
* @param {Element} a
|
||||
* @param {Element} b The target element
|
||||
* @return {boolean}
|
||||
*/
|
||||
function compareNodeNames(fromEl, toEl) {
|
||||
var fromNodeName = fromEl.nodeName;
|
||||
var toNodeName = toEl.nodeName;
|
||||
var fromCodeStart, toCodeStart;
|
||||
|
||||
if (fromNodeName === toNodeName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
fromCodeStart = fromNodeName.charCodeAt(0);
|
||||
toCodeStart = toNodeName.charCodeAt(0);
|
||||
|
||||
// If the target element is a virtual DOM node or SVG node then we may
|
||||
// need to normalize the tag name before comparing. Normal HTML elements that are
|
||||
// in the "http://www.w3.org/1999/xhtml"
|
||||
// are converted to upper case
|
||||
if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower
|
||||
return fromNodeName === toNodeName.toUpperCase();
|
||||
} else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower
|
||||
return toNodeName === fromNodeName.toUpperCase();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an element, optionally with a known namespace URI.
|
||||
*
|
||||
* @param {string} name the element name, e.g. 'div' or 'svg'
|
||||
* @param {string} [namespaceURI] the element's namespace URI, i.e. the value of
|
||||
* its `xmlns` attribute or its inferred namespace.
|
||||
*
|
||||
* @return {Element}
|
||||
*/
|
||||
function createElementNS(name, namespaceURI) {
|
||||
return !namespaceURI || namespaceURI === NS_XHTML ?
|
||||
doc.createElement(name) :
|
||||
doc.createElementNS(namespaceURI, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the children of one DOM element to another DOM element
|
||||
*/
|
||||
function moveChildren(fromEl, toEl) {
|
||||
var curChild = fromEl.firstChild;
|
||||
while (curChild) {
|
||||
var nextChild = curChild.nextSibling;
|
||||
toEl.appendChild(curChild);
|
||||
curChild = nextChild;
|
||||
}
|
||||
return toEl;
|
||||
}
|
||||
|
||||
function syncBooleanAttrProp(fromEl, toEl, name) {
|
||||
if (fromEl[name] !== toEl[name]) {
|
||||
fromEl[name] = toEl[name];
|
||||
if (fromEl[name]) {
|
||||
fromEl.setAttribute(name, '');
|
||||
} else {
|
||||
fromEl.removeAttribute(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var specialElHandlers = {
|
||||
OPTION: function(fromEl, toEl) {
|
||||
var parentNode = fromEl.parentNode;
|
||||
if (parentNode) {
|
||||
var parentName = parentNode.nodeName.toUpperCase();
|
||||
if (parentName === 'OPTGROUP') {
|
||||
parentNode = parentNode.parentNode;
|
||||
parentName = parentNode && parentNode.nodeName.toUpperCase();
|
||||
}
|
||||
if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {
|
||||
if (fromEl.hasAttribute('selected') && !toEl.selected) {
|
||||
// Workaround for MS Edge bug where the 'selected' attribute can only be
|
||||
// removed if set to a non-empty value:
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/
|
||||
fromEl.setAttribute('selected', 'selected');
|
||||
fromEl.removeAttribute('selected');
|
||||
}
|
||||
// We have to reset select element's selectedIndex to -1, otherwise setting
|
||||
// fromEl.selected using the syncBooleanAttrProp below has no effect.
|
||||
// The correct selectedIndex will be set in the SELECT special handler below.
|
||||
parentNode.selectedIndex = -1;
|
||||
}
|
||||
}
|
||||
syncBooleanAttrProp(fromEl, toEl, 'selected');
|
||||
},
|
||||
/**
|
||||
* The "value" attribute is special for the <input> element since it sets
|
||||
* the initial value. Changing the "value" attribute without changing the
|
||||
* "value" property will have no effect since it is only used to the set the
|
||||
* initial value. Similar for the "checked" attribute, and "disabled".
|
||||
*/
|
||||
INPUT: function(fromEl, toEl) {
|
||||
syncBooleanAttrProp(fromEl, toEl, 'checked');
|
||||
syncBooleanAttrProp(fromEl, toEl, 'disabled');
|
||||
|
||||
if (fromEl.value !== toEl.value) {
|
||||
fromEl.value = toEl.value;
|
||||
}
|
||||
|
||||
if (!toEl.hasAttribute('value')) {
|
||||
fromEl.removeAttribute('value');
|
||||
}
|
||||
},
|
||||
|
||||
TEXTAREA: function(fromEl, toEl) {
|
||||
var newValue = toEl.value;
|
||||
if (fromEl.value !== newValue) {
|
||||
fromEl.value = newValue;
|
||||
}
|
||||
|
||||
var firstChild = fromEl.firstChild;
|
||||
if (firstChild) {
|
||||
// Needed for IE. Apparently IE sets the placeholder as the
|
||||
// node value and vise versa. This ignores an empty update.
|
||||
var oldValue = firstChild.nodeValue;
|
||||
|
||||
if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
firstChild.nodeValue = newValue;
|
||||
}
|
||||
},
|
||||
SELECT: function(fromEl, toEl) {
|
||||
if (!toEl.hasAttribute('multiple')) {
|
||||
var selectedIndex = -1;
|
||||
var i = 0;
|
||||
// We have to loop through children of fromEl, not toEl since nodes can be moved
|
||||
// from toEl to fromEl directly when morphing.
|
||||
// At the time this special handler is invoked, all children have already been morphed
|
||||
// and appended to / removed from fromEl, so using fromEl here is safe and correct.
|
||||
var curChild = fromEl.firstChild;
|
||||
var optgroup;
|
||||
var nodeName;
|
||||
while(curChild) {
|
||||
nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();
|
||||
if (nodeName === 'OPTGROUP') {
|
||||
optgroup = curChild;
|
||||
curChild = optgroup.firstChild;
|
||||
} else {
|
||||
if (nodeName === 'OPTION') {
|
||||
if (curChild.hasAttribute('selected')) {
|
||||
selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
curChild = curChild.nextSibling;
|
||||
if (!curChild && optgroup) {
|
||||
curChild = optgroup.nextSibling;
|
||||
optgroup = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fromEl.selectedIndex = selectedIndex;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var ELEMENT_NODE = 1;
|
||||
var DOCUMENT_FRAGMENT_NODE$1 = 11;
|
||||
var TEXT_NODE = 3;
|
||||
var COMMENT_NODE = 8;
|
||||
|
||||
function noop() {}
|
||||
|
||||
function defaultGetNodeKey(node) {
|
||||
if (node) {
|
||||
return (node.getAttribute && node.getAttribute('id')) || node.id;
|
||||
}
|
||||
}
|
||||
|
||||
function morphdomFactory(morphAttrs) {
|
||||
|
||||
return function morphdom(fromNode, toNode, options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (typeof toNode === 'string') {
|
||||
if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {
|
||||
var toNodeHtml = toNode;
|
||||
toNode = doc.createElement('html');
|
||||
toNode.innerHTML = toNodeHtml;
|
||||
} else {
|
||||
toNode = toElement(toNode);
|
||||
}
|
||||
}
|
||||
|
||||
var getNodeKey = options.getNodeKey || defaultGetNodeKey;
|
||||
var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
|
||||
var onNodeAdded = options.onNodeAdded || noop;
|
||||
var onBeforeElUpdated = options.onBeforeElUpdated || noop;
|
||||
var onElUpdated = options.onElUpdated || noop;
|
||||
var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
|
||||
var onNodeDiscarded = options.onNodeDiscarded || noop;
|
||||
var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
|
||||
var childrenOnly = options.childrenOnly === true;
|
||||
|
||||
// This object is used as a lookup to quickly find all keyed elements in the original DOM tree.
|
||||
var fromNodesLookup = Object.create(null);
|
||||
var keyedRemovalList = [];
|
||||
|
||||
function addKeyedRemoval(key) {
|
||||
keyedRemovalList.push(key);
|
||||
}
|
||||
|
||||
function walkDiscardedChildNodes(node, skipKeyedNodes) {
|
||||
if (node.nodeType === ELEMENT_NODE) {
|
||||
var curChild = node.firstChild;
|
||||
while (curChild) {
|
||||
|
||||
var key = undefined;
|
||||
|
||||
if (skipKeyedNodes && (key = getNodeKey(curChild))) {
|
||||
// If we are skipping keyed nodes then we add the key
|
||||
// to a list so that it can be handled at the very end.
|
||||
addKeyedRemoval(key);
|
||||
} else {
|
||||
// Only report the node as discarded if it is not keyed. We do this because
|
||||
// at the end we loop through all keyed elements that were unmatched
|
||||
// and then discard them in one final pass.
|
||||
onNodeDiscarded(curChild);
|
||||
if (curChild.firstChild) {
|
||||
walkDiscardedChildNodes(curChild, skipKeyedNodes);
|
||||
}
|
||||
}
|
||||
|
||||
curChild = curChild.nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a DOM node out of the original DOM
|
||||
*
|
||||
* @param {Node} node The node to remove
|
||||
* @param {Node} parentNode The nodes parent
|
||||
* @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.
|
||||
* @return {undefined}
|
||||
*/
|
||||
function removeNode(node, parentNode, skipKeyedNodes) {
|
||||
if (onBeforeNodeDiscarded(node) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parentNode) {
|
||||
parentNode.removeChild(node);
|
||||
}
|
||||
|
||||
onNodeDiscarded(node);
|
||||
walkDiscardedChildNodes(node, skipKeyedNodes);
|
||||
}
|
||||
|
||||
// // TreeWalker implementation is no faster, but keeping this around in case this changes in the future
|
||||
// function indexTree(root) {
|
||||
// var treeWalker = document.createTreeWalker(
|
||||
// root,
|
||||
// NodeFilter.SHOW_ELEMENT);
|
||||
//
|
||||
// var el;
|
||||
// while((el = treeWalker.nextNode())) {
|
||||
// var key = getNodeKey(el);
|
||||
// if (key) {
|
||||
// fromNodesLookup[key] = el;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // NodeIterator implementation is no faster, but keeping this around in case this changes in the future
|
||||
//
|
||||
// function indexTree(node) {
|
||||
// var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);
|
||||
// var el;
|
||||
// while((el = nodeIterator.nextNode())) {
|
||||
// var key = getNodeKey(el);
|
||||
// if (key) {
|
||||
// fromNodesLookup[key] = el;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
function indexTree(node) {
|
||||
if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
|
||||
var curChild = node.firstChild;
|
||||
while (curChild) {
|
||||
var key = getNodeKey(curChild);
|
||||
if (key) {
|
||||
fromNodesLookup[key] = curChild;
|
||||
}
|
||||
|
||||
// Walk recursively
|
||||
indexTree(curChild);
|
||||
|
||||
curChild = curChild.nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indexTree(fromNode);
|
||||
|
||||
function handleNodeAdded(el) {
|
||||
onNodeAdded(el);
|
||||
|
||||
var curChild = el.firstChild;
|
||||
while (curChild) {
|
||||
var nextSibling = curChild.nextSibling;
|
||||
|
||||
var key = getNodeKey(curChild);
|
||||
if (key) {
|
||||
var unmatchedFromEl = fromNodesLookup[key];
|
||||
// if we find a duplicate #id node in cache, replace `el` with cache value
|
||||
// and morph it to the child node.
|
||||
if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {
|
||||
curChild.parentNode.replaceChild(unmatchedFromEl, curChild);
|
||||
morphEl(unmatchedFromEl, curChild);
|
||||
} else {
|
||||
handleNodeAdded(curChild);
|
||||
}
|
||||
} else {
|
||||
// recursively call for curChild and it's children to see if we find something in
|
||||
// fromNodesLookup
|
||||
handleNodeAdded(curChild);
|
||||
}
|
||||
|
||||
curChild = nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {
|
||||
// We have processed all of the "to nodes". If curFromNodeChild is
|
||||
// non-null then we still have some from nodes left over that need
|
||||
// to be removed
|
||||
while (curFromNodeChild) {
|
||||
var fromNextSibling = curFromNodeChild.nextSibling;
|
||||
if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {
|
||||
// Since the node is keyed it might be matched up later so we defer
|
||||
// the actual removal to later
|
||||
addKeyedRemoval(curFromNodeKey);
|
||||
} else {
|
||||
// NOTE: we skip nested keyed nodes from being removed since there is
|
||||
// still a chance they will be matched up later
|
||||
removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
|
||||
}
|
||||
curFromNodeChild = fromNextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
function morphEl(fromEl, toEl, childrenOnly) {
|
||||
var toElKey = getNodeKey(toEl);
|
||||
|
||||
if (toElKey) {
|
||||
// If an element with an ID is being morphed then it will be in the final
|
||||
// DOM so clear it out of the saved elements collection
|
||||
delete fromNodesLookup[toElKey];
|
||||
}
|
||||
|
||||
if (!childrenOnly) {
|
||||
// optional
|
||||
if (onBeforeElUpdated(fromEl, toEl) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update attributes on original DOM element first
|
||||
morphAttrs(fromEl, toEl);
|
||||
// optional
|
||||
onElUpdated(fromEl);
|
||||
|
||||
if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (fromEl.nodeName !== 'TEXTAREA') {
|
||||
morphChildren(fromEl, toEl);
|
||||
} else {
|
||||
specialElHandlers.TEXTAREA(fromEl, toEl);
|
||||
}
|
||||
}
|
||||
|
||||
function morphChildren(fromEl, toEl) {
|
||||
var curToNodeChild = toEl.firstChild;
|
||||
var curFromNodeChild = fromEl.firstChild;
|
||||
var curToNodeKey;
|
||||
var curFromNodeKey;
|
||||
|
||||
var fromNextSibling;
|
||||
var toNextSibling;
|
||||
var matchingFromEl;
|
||||
|
||||
// walk the children
|
||||
outer: while (curToNodeChild) {
|
||||
toNextSibling = curToNodeChild.nextSibling;
|
||||
curToNodeKey = getNodeKey(curToNodeChild);
|
||||
|
||||
// walk the fromNode children all the way through
|
||||
while (curFromNodeChild) {
|
||||
fromNextSibling = curFromNodeChild.nextSibling;
|
||||
|
||||
if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
|
||||
curToNodeChild = toNextSibling;
|
||||
curFromNodeChild = fromNextSibling;
|
||||
continue outer;
|
||||
}
|
||||
|
||||
curFromNodeKey = getNodeKey(curFromNodeChild);
|
||||
|
||||
var curFromNodeType = curFromNodeChild.nodeType;
|
||||
|
||||
// this means if the curFromNodeChild doesnt have a match with the curToNodeChild
|
||||
var isCompatible = undefined;
|
||||
|
||||
if (curFromNodeType === curToNodeChild.nodeType) {
|
||||
if (curFromNodeType === ELEMENT_NODE) {
|
||||
// Both nodes being compared are Element nodes
|
||||
|
||||
if (curToNodeKey) {
|
||||
// The target node has a key so we want to match it up with the correct element
|
||||
// in the original DOM tree
|
||||
if (curToNodeKey !== curFromNodeKey) {
|
||||
// The current element in the original DOM tree does not have a matching key so
|
||||
// let's check our lookup to see if there is a matching element in the original
|
||||
// DOM tree
|
||||
if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {
|
||||
if (fromNextSibling === matchingFromEl) {
|
||||
// Special case for single element removals. To avoid removing the original
|
||||
// DOM node out of the tree (since that can break CSS transitions, etc.),
|
||||
// we will instead discard the current node and wait until the next
|
||||
// iteration to properly match up the keyed target element with its matching
|
||||
// element in the original tree
|
||||
isCompatible = false;
|
||||
} else {
|
||||
// We found a matching keyed element somewhere in the original DOM tree.
|
||||
// Let's move the original DOM node into the current position and morph
|
||||
// it.
|
||||
|
||||
// NOTE: We use insertBefore instead of replaceChild because we want to go through
|
||||
// the `removeNode()` function for the node that is being discarded so that
|
||||
// all lifecycle hooks are correctly invoked
|
||||
fromEl.insertBefore(matchingFromEl, curFromNodeChild);
|
||||
|
||||
// fromNextSibling = curFromNodeChild.nextSibling;
|
||||
|
||||
if (curFromNodeKey) {
|
||||
// Since the node is keyed it might be matched up later so we defer
|
||||
// the actual removal to later
|
||||
addKeyedRemoval(curFromNodeKey);
|
||||
} else {
|
||||
// NOTE: we skip nested keyed nodes from being removed since there is
|
||||
// still a chance they will be matched up later
|
||||
removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
|
||||
}
|
||||
|
||||
curFromNodeChild = matchingFromEl;
|
||||
}
|
||||
} else {
|
||||
// The nodes are not compatible since the "to" node has a key and there
|
||||
// is no matching keyed node in the source tree
|
||||
isCompatible = false;
|
||||
}
|
||||
}
|
||||
} else if (curFromNodeKey) {
|
||||
// The original has a key
|
||||
isCompatible = false;
|
||||
}
|
||||
|
||||
isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);
|
||||
if (isCompatible) {
|
||||
// We found compatible DOM elements so transform
|
||||
// the current "from" node to match the current
|
||||
// target DOM node.
|
||||
// MORPH
|
||||
morphEl(curFromNodeChild, curToNodeChild);
|
||||
}
|
||||
|
||||
} else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {
|
||||
// Both nodes being compared are Text or Comment nodes
|
||||
isCompatible = true;
|
||||
// Simply update nodeValue on the original node to
|
||||
// change the text value
|
||||
if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {
|
||||
curFromNodeChild.nodeValue = curToNodeChild.nodeValue;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (isCompatible) {
|
||||
// Advance both the "to" child and the "from" child since we found a match
|
||||
// Nothing else to do as we already recursively called morphChildren above
|
||||
curToNodeChild = toNextSibling;
|
||||
curFromNodeChild = fromNextSibling;
|
||||
continue outer;
|
||||
}
|
||||
|
||||
// No compatible match so remove the old node from the DOM and continue trying to find a
|
||||
// match in the original DOM. However, we only do this if the from node is not keyed
|
||||
// since it is possible that a keyed node might match up with a node somewhere else in the
|
||||
// target tree and we don't want to discard it just yet since it still might find a
|
||||
// home in the final DOM tree. After everything is done we will remove any keyed nodes
|
||||
// that didn't find a home
|
||||
if (curFromNodeKey) {
|
||||
// Since the node is keyed it might be matched up later so we defer
|
||||
// the actual removal to later
|
||||
addKeyedRemoval(curFromNodeKey);
|
||||
} else {
|
||||
// NOTE: we skip nested keyed nodes from being removed since there is
|
||||
// still a chance they will be matched up later
|
||||
removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
|
||||
}
|
||||
|
||||
curFromNodeChild = fromNextSibling;
|
||||
} // END: while(curFromNodeChild) {}
|
||||
|
||||
// If we got this far then we did not find a candidate match for
|
||||
// our "to node" and we exhausted all of the children "from"
|
||||
// nodes. Therefore, we will just append the current "to" node
|
||||
// to the end
|
||||
if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
|
||||
fromEl.appendChild(matchingFromEl);
|
||||
// MORPH
|
||||
morphEl(matchingFromEl, curToNodeChild);
|
||||
} else {
|
||||
var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
|
||||
if (onBeforeNodeAddedResult !== false) {
|
||||
if (onBeforeNodeAddedResult) {
|
||||
curToNodeChild = onBeforeNodeAddedResult;
|
||||
}
|
||||
|
||||
if (curToNodeChild.actualize) {
|
||||
curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
|
||||
}
|
||||
fromEl.appendChild(curToNodeChild);
|
||||
handleNodeAdded(curToNodeChild);
|
||||
}
|
||||
}
|
||||
|
||||
curToNodeChild = toNextSibling;
|
||||
curFromNodeChild = fromNextSibling;
|
||||
}
|
||||
|
||||
cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);
|
||||
|
||||
var specialElHandler = specialElHandlers[fromEl.nodeName];
|
||||
if (specialElHandler) {
|
||||
specialElHandler(fromEl, toEl);
|
||||
}
|
||||
} // END: morphChildren(...)
|
||||
|
||||
var morphedNode = fromNode;
|
||||
var morphedNodeType = morphedNode.nodeType;
|
||||
var toNodeType = toNode.nodeType;
|
||||
|
||||
if (!childrenOnly) {
|
||||
// Handle the case where we are given two DOM nodes that are not
|
||||
// compatible (e.g. <div> --> <span> or <div> --> TEXT)
|
||||
if (morphedNodeType === ELEMENT_NODE) {
|
||||
if (toNodeType === ELEMENT_NODE) {
|
||||
if (!compareNodeNames(fromNode, toNode)) {
|
||||
onNodeDiscarded(fromNode);
|
||||
morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));
|
||||
}
|
||||
} else {
|
||||
// Going from an element node to a text node
|
||||
morphedNode = toNode;
|
||||
}
|
||||
} else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node
|
||||
if (toNodeType === morphedNodeType) {
|
||||
if (morphedNode.nodeValue !== toNode.nodeValue) {
|
||||
morphedNode.nodeValue = toNode.nodeValue;
|
||||
}
|
||||
|
||||
return morphedNode;
|
||||
} else {
|
||||
// Text node to something else
|
||||
morphedNode = toNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (morphedNode === toNode) {
|
||||
// The "to node" was not compatible with the "from node" so we had to
|
||||
// toss out the "from node" and use the "to node"
|
||||
onNodeDiscarded(fromNode);
|
||||
} else {
|
||||
if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
morphEl(morphedNode, toNode, childrenOnly);
|
||||
|
||||
// We now need to loop over any keyed nodes that might need to be
|
||||
// removed. We only do the removal if we know that the keyed node
|
||||
// never found a match. When a keyed node is matched up we remove
|
||||
// it out of fromNodesLookup and we use fromNodesLookup to determine
|
||||
// if a keyed node has been matched up or not
|
||||
if (keyedRemovalList) {
|
||||
for (var i=0, len=keyedRemovalList.length; i<len; i++) {
|
||||
var elToRemove = fromNodesLookup[keyedRemovalList[i]];
|
||||
if (elToRemove) {
|
||||
removeNode(elToRemove, elToRemove.parentNode, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {
|
||||
if (morphedNode.actualize) {
|
||||
morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc);
|
||||
}
|
||||
// If we had to swap out the from node with a new node because the old
|
||||
// node was not compatible with the target node then we need to
|
||||
// replace the old DOM node in the original DOM tree. This is only
|
||||
// possible if the original DOM node was part of a DOM tree which
|
||||
// we know is the case if it has a parent node.
|
||||
fromNode.parentNode.replaceChild(morphedNode, fromNode);
|
||||
}
|
||||
|
||||
return morphedNode;
|
||||
};
|
||||
}
|
||||
|
||||
var morphdom = morphdomFactory(morphAttrs);
|
||||
|
||||
return morphdom;
|
||||
|
||||
}));
|
740
www/test/0.1.1/test/lib/mustache.js
Normal file
740
www/test/0.1.1/test/lib/mustache.js
Normal file
@ -0,0 +1,740 @@
|
||||
// This file has been generated from mustache.mjs
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global = global || self, global.Mustache = factory());
|
||||
}(this, (function () { 'use strict';
|
||||
|
||||
/*!
|
||||
* mustache.js - Logic-less {{mustache}} templates with JavaScript
|
||||
* http://github.com/janl/mustache.js
|
||||
*/
|
||||
|
||||
var objectToString = Object.prototype.toString;
|
||||
var isArray = Array.isArray || function isArrayPolyfill (object) {
|
||||
return objectToString.call(object) === '[object Array]';
|
||||
};
|
||||
|
||||
function isFunction (object) {
|
||||
return typeof object === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* More correct typeof string handling array
|
||||
* which normally returns typeof 'object'
|
||||
*/
|
||||
function typeStr (obj) {
|
||||
return isArray(obj) ? 'array' : typeof obj;
|
||||
}
|
||||
|
||||
function escapeRegExp (string) {
|
||||
return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Null safe way of checking whether or not an object,
|
||||
* including its prototype, has a given property
|
||||
*/
|
||||
function hasProperty (obj, propName) {
|
||||
return obj != null && typeof obj === 'object' && (propName in obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe way of detecting whether or not the given thing is a primitive and
|
||||
* whether it has the given property
|
||||
*/
|
||||
function primitiveHasOwnProperty (primitive, propName) {
|
||||
return (
|
||||
primitive != null
|
||||
&& typeof primitive !== 'object'
|
||||
&& primitive.hasOwnProperty
|
||||
&& primitive.hasOwnProperty(propName)
|
||||
);
|
||||
}
|
||||
|
||||
// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
|
||||
// See https://github.com/janl/mustache.js/issues/189
|
||||
var regExpTest = RegExp.prototype.test;
|
||||
function testRegExp (re, string) {
|
||||
return regExpTest.call(re, string);
|
||||
}
|
||||
|
||||
var nonSpaceRe = /\S/;
|
||||
function isWhitespace (string) {
|
||||
return !testRegExp(nonSpaceRe, string);
|
||||
}
|
||||
|
||||
var entityMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'/': '/',
|
||||
'`': '`',
|
||||
'=': '='
|
||||
};
|
||||
|
||||
function escapeHtml (string) {
|
||||
return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
|
||||
return entityMap[s];
|
||||
});
|
||||
}
|
||||
|
||||
var whiteRe = /\s*/;
|
||||
var spaceRe = /\s+/;
|
||||
var equalsRe = /\s*=/;
|
||||
var curlyRe = /\s*\}/;
|
||||
var tagRe = /#|\^|\/|>|\{|&|=|!/;
|
||||
|
||||
/**
|
||||
* Breaks up the given `template` string into a tree of tokens. If the `tags`
|
||||
* argument is given here it must be an array with two string values: the
|
||||
* opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of
|
||||
* course, the default is to use mustaches (i.e. mustache.tags).
|
||||
*
|
||||
* A token is an array with at least 4 elements. The first element is the
|
||||
* mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag
|
||||
* did not contain a symbol (i.e. {{myValue}}) this element is "name". For
|
||||
* all text that appears outside a symbol this element is "text".
|
||||
*
|
||||
* The second element of a token is its "value". For mustache tags this is
|
||||
* whatever else was inside the tag besides the opening symbol. For text tokens
|
||||
* this is the text itself.
|
||||
*
|
||||
* The third and fourth elements of the token are the start and end indices,
|
||||
* respectively, of the token in the original template.
|
||||
*
|
||||
* Tokens that are the root node of a subtree contain two more elements: 1) an
|
||||
* array of tokens in the subtree and 2) the index in the original template at
|
||||
* which the closing tag for that section begins.
|
||||
*
|
||||
* Tokens for partials also contain two more elements: 1) a string value of
|
||||
* indendation prior to that tag and 2) the index of that tag on that line -
|
||||
* eg a value of 2 indicates the partial is the third tag on this line.
|
||||
*/
|
||||
function parseTemplate (template, tags) {
|
||||
if (!template)
|
||||
return [];
|
||||
var lineHasNonSpace = false;
|
||||
var sections = []; // Stack to hold section tokens
|
||||
var tokens = []; // Buffer to hold the tokens
|
||||
var spaces = []; // Indices of whitespace tokens on the current line
|
||||
var hasTag = false; // Is there a {{tag}} on the current line?
|
||||
var nonSpace = false; // Is there a non-space char on the current line?
|
||||
var indentation = ''; // Tracks indentation for tags that use it
|
||||
var tagIndex = 0; // Stores a count of number of tags encountered on a line
|
||||
|
||||
// Strips all whitespace tokens array for the current line
|
||||
// if there was a {{#tag}} on it and otherwise only space.
|
||||
function stripSpace () {
|
||||
if (hasTag && !nonSpace) {
|
||||
while (spaces.length)
|
||||
delete tokens[spaces.pop()];
|
||||
} else {
|
||||
spaces = [];
|
||||
}
|
||||
|
||||
hasTag = false;
|
||||
nonSpace = false;
|
||||
}
|
||||
|
||||
var openingTagRe, closingTagRe, closingCurlyRe;
|
||||
function compileTags (tagsToCompile) {
|
||||
if (typeof tagsToCompile === 'string')
|
||||
tagsToCompile = tagsToCompile.split(spaceRe, 2);
|
||||
|
||||
if (!isArray(tagsToCompile) || tagsToCompile.length !== 2)
|
||||
throw new Error('Invalid tags: ' + tagsToCompile);
|
||||
|
||||
openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*');
|
||||
closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1]));
|
||||
closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1]));
|
||||
}
|
||||
|
||||
compileTags(tags || mustache.tags);
|
||||
|
||||
var scanner = new Scanner(template);
|
||||
|
||||
var start, type, value, chr, token, openSection;
|
||||
while (!scanner.eos()) {
|
||||
start = scanner.pos;
|
||||
|
||||
// Match any text between tags.
|
||||
value = scanner.scanUntil(openingTagRe);
|
||||
|
||||
if (value) {
|
||||
for (var i = 0, valueLength = value.length; i < valueLength; ++i) {
|
||||
chr = value.charAt(i);
|
||||
|
||||
if (isWhitespace(chr)) {
|
||||
spaces.push(tokens.length);
|
||||
indentation += chr;
|
||||
} else {
|
||||
nonSpace = true;
|
||||
lineHasNonSpace = true;
|
||||
indentation += ' ';
|
||||
}
|
||||
|
||||
tokens.push([ 'text', chr, start, start + 1 ]);
|
||||
start += 1;
|
||||
|
||||
// Check for whitespace on the current line.
|
||||
if (chr === '\n') {
|
||||
stripSpace();
|
||||
indentation = '';
|
||||
tagIndex = 0;
|
||||
lineHasNonSpace = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match the opening tag.
|
||||
if (!scanner.scan(openingTagRe))
|
||||
break;
|
||||
|
||||
hasTag = true;
|
||||
|
||||
// Get the tag type.
|
||||
type = scanner.scan(tagRe) || 'name';
|
||||
scanner.scan(whiteRe);
|
||||
|
||||
// Get the tag value.
|
||||
if (type === '=') {
|
||||
value = scanner.scanUntil(equalsRe);
|
||||
scanner.scan(equalsRe);
|
||||
scanner.scanUntil(closingTagRe);
|
||||
} else if (type === '{') {
|
||||
value = scanner.scanUntil(closingCurlyRe);
|
||||
scanner.scan(curlyRe);
|
||||
scanner.scanUntil(closingTagRe);
|
||||
type = '&';
|
||||
} else {
|
||||
value = scanner.scanUntil(closingTagRe);
|
||||
}
|
||||
|
||||
// Match the closing tag.
|
||||
if (!scanner.scan(closingTagRe))
|
||||
throw new Error('Unclosed tag at ' + scanner.pos);
|
||||
|
||||
if (type == '>') {
|
||||
token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ];
|
||||
} else {
|
||||
token = [ type, value, start, scanner.pos ];
|
||||
}
|
||||
tagIndex++;
|
||||
tokens.push(token);
|
||||
|
||||
if (type === '#' || type === '^') {
|
||||
sections.push(token);
|
||||
} else if (type === '/') {
|
||||
// Check section nesting.
|
||||
openSection = sections.pop();
|
||||
|
||||
if (!openSection)
|
||||
throw new Error('Unopened section "' + value + '" at ' + start);
|
||||
|
||||
if (openSection[1] !== value)
|
||||
throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
|
||||
} else if (type === 'name' || type === '{' || type === '&') {
|
||||
nonSpace = true;
|
||||
} else if (type === '=') {
|
||||
// Set the tags for the next time around.
|
||||
compileTags(value);
|
||||
}
|
||||
}
|
||||
|
||||
stripSpace();
|
||||
|
||||
// Make sure there are no open sections when we're done.
|
||||
openSection = sections.pop();
|
||||
|
||||
if (openSection)
|
||||
throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
|
||||
|
||||
return nestTokens(squashTokens(tokens));
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the values of consecutive text tokens in the given `tokens` array
|
||||
* to a single token.
|
||||
*/
|
||||
function squashTokens (tokens) {
|
||||
var squashedTokens = [];
|
||||
|
||||
var token, lastToken;
|
||||
for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
|
||||
token = tokens[i];
|
||||
|
||||
if (token) {
|
||||
if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
|
||||
lastToken[1] += token[1];
|
||||
lastToken[3] = token[3];
|
||||
} else {
|
||||
squashedTokens.push(token);
|
||||
lastToken = token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return squashedTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forms the given array of `tokens` into a nested tree structure where
|
||||
* tokens that represent a section have two additional items: 1) an array of
|
||||
* all tokens that appear in that section and 2) the index in the original
|
||||
* template that represents the end of that section.
|
||||
*/
|
||||
function nestTokens (tokens) {
|
||||
var nestedTokens = [];
|
||||
var collector = nestedTokens;
|
||||
var sections = [];
|
||||
|
||||
var token, section;
|
||||
for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
|
||||
token = tokens[i];
|
||||
|
||||
switch (token[0]) {
|
||||
case '#':
|
||||
case '^':
|
||||
collector.push(token);
|
||||
sections.push(token);
|
||||
collector = token[4] = [];
|
||||
break;
|
||||
case '/':
|
||||
section = sections.pop();
|
||||
section[5] = token[2];
|
||||
collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
|
||||
break;
|
||||
default:
|
||||
collector.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
return nestedTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple string scanner that is used by the template parser to find
|
||||
* tokens in template strings.
|
||||
*/
|
||||
function Scanner (string) {
|
||||
this.string = string;
|
||||
this.tail = string;
|
||||
this.pos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the tail is empty (end of string).
|
||||
*/
|
||||
Scanner.prototype.eos = function eos () {
|
||||
return this.tail === '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Tries to match the given regular expression at the current position.
|
||||
* Returns the matched text if it can match, the empty string otherwise.
|
||||
*/
|
||||
Scanner.prototype.scan = function scan (re) {
|
||||
var match = this.tail.match(re);
|
||||
|
||||
if (!match || match.index !== 0)
|
||||
return '';
|
||||
|
||||
var string = match[0];
|
||||
|
||||
this.tail = this.tail.substring(string.length);
|
||||
this.pos += string.length;
|
||||
|
||||
return string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Skips all text until the given regular expression can be matched. Returns
|
||||
* the skipped string, which is the entire tail if no match can be made.
|
||||
*/
|
||||
Scanner.prototype.scanUntil = function scanUntil (re) {
|
||||
var index = this.tail.search(re), match;
|
||||
|
||||
switch (index) {
|
||||
case -1:
|
||||
match = this.tail;
|
||||
this.tail = '';
|
||||
break;
|
||||
case 0:
|
||||
match = '';
|
||||
break;
|
||||
default:
|
||||
match = this.tail.substring(0, index);
|
||||
this.tail = this.tail.substring(index);
|
||||
}
|
||||
|
||||
this.pos += match.length;
|
||||
|
||||
return match;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a rendering context by wrapping a view object and
|
||||
* maintaining a reference to the parent context.
|
||||
*/
|
||||
function Context (view, parentContext) {
|
||||
this.view = view;
|
||||
this.cache = { '.': this.view };
|
||||
this.parent = parentContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new context using the given view with this context
|
||||
* as the parent.
|
||||
*/
|
||||
Context.prototype.push = function push (view) {
|
||||
return new Context(view, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the value of the given name in this context, traversing
|
||||
* up the context hierarchy if the value is absent in this context's view.
|
||||
*/
|
||||
Context.prototype.lookup = function lookup (name) {
|
||||
var cache = this.cache;
|
||||
|
||||
var value;
|
||||
if (cache.hasOwnProperty(name)) {
|
||||
value = cache[name];
|
||||
} else {
|
||||
var context = this, intermediateValue, names, index, lookupHit = false;
|
||||
|
||||
while (context) {
|
||||
if (name.indexOf('.') > 0) {
|
||||
intermediateValue = context.view;
|
||||
names = name.split('.');
|
||||
index = 0;
|
||||
|
||||
/**
|
||||
* Using the dot notion path in `name`, we descend through the
|
||||
* nested objects.
|
||||
*
|
||||
* To be certain that the lookup has been successful, we have to
|
||||
* check if the last object in the path actually has the property
|
||||
* we are looking for. We store the result in `lookupHit`.
|
||||
*
|
||||
* This is specially necessary for when the value has been set to
|
||||
* `undefined` and we want to avoid looking up parent contexts.
|
||||
*
|
||||
* In the case where dot notation is used, we consider the lookup
|
||||
* to be successful even if the last "object" in the path is
|
||||
* not actually an object but a primitive (e.g., a string, or an
|
||||
* integer), because it is sometimes useful to access a property
|
||||
* of an autoboxed primitive, such as the length of a string.
|
||||
**/
|
||||
while (intermediateValue != null && index < names.length) {
|
||||
if (index === names.length - 1)
|
||||
lookupHit = (
|
||||
hasProperty(intermediateValue, names[index])
|
||||
|| primitiveHasOwnProperty(intermediateValue, names[index])
|
||||
);
|
||||
|
||||
intermediateValue = intermediateValue[names[index++]];
|
||||
}
|
||||
} else {
|
||||
intermediateValue = context.view[name];
|
||||
|
||||
/**
|
||||
* Only checking against `hasProperty`, which always returns `false` if
|
||||
* `context.view` is not an object. Deliberately omitting the check
|
||||
* against `primitiveHasOwnProperty` if dot notation is not used.
|
||||
*
|
||||
* Consider this example:
|
||||
* ```
|
||||
* Mustache.render("The length of a football field is {{#length}}{{length}}{{/length}}.", {length: "100 yards"})
|
||||
* ```
|
||||
*
|
||||
* If we were to check also against `primitiveHasOwnProperty`, as we do
|
||||
* in the dot notation case, then render call would return:
|
||||
*
|
||||
* "The length of a football field is 9."
|
||||
*
|
||||
* rather than the expected:
|
||||
*
|
||||
* "The length of a football field is 100 yards."
|
||||
**/
|
||||
lookupHit = hasProperty(context.view, name);
|
||||
}
|
||||
|
||||
if (lookupHit) {
|
||||
value = intermediateValue;
|
||||
break;
|
||||
}
|
||||
|
||||
context = context.parent;
|
||||
}
|
||||
|
||||
cache[name] = value;
|
||||
}
|
||||
|
||||
if (isFunction(value))
|
||||
value = value.call(this.view);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Writer knows how to take a stream of tokens and render them to a
|
||||
* string, given a context. It also maintains a cache of templates to
|
||||
* avoid the need to parse the same template twice.
|
||||
*/
|
||||
function Writer () {
|
||||
this.templateCache = {
|
||||
_cache: {},
|
||||
set: function set (key, value) {
|
||||
this._cache[key] = value;
|
||||
},
|
||||
get: function get (key) {
|
||||
return this._cache[key];
|
||||
},
|
||||
clear: function clear () {
|
||||
this._cache = {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all cached templates in this writer.
|
||||
*/
|
||||
Writer.prototype.clearCache = function clearCache () {
|
||||
if (typeof this.templateCache !== 'undefined') {
|
||||
this.templateCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses and caches the given `template` according to the given `tags` or
|
||||
* `mustache.tags` if `tags` is omitted, and returns the array of tokens
|
||||
* that is generated from the parse.
|
||||
*/
|
||||
Writer.prototype.parse = function parse (template, tags) {
|
||||
var cache = this.templateCache;
|
||||
var cacheKey = template + ':' + (tags || mustache.tags).join(':');
|
||||
var isCacheEnabled = typeof cache !== 'undefined';
|
||||
var tokens = isCacheEnabled ? cache.get(cacheKey) : undefined;
|
||||
|
||||
if (tokens == undefined) {
|
||||
tokens = parseTemplate(template, tags);
|
||||
isCacheEnabled && cache.set(cacheKey, tokens);
|
||||
}
|
||||
return tokens;
|
||||
};
|
||||
|
||||
/**
|
||||
* High-level method that is used to render the given `template` with
|
||||
* the given `view`.
|
||||
*
|
||||
* The optional `partials` argument may be an object that contains the
|
||||
* names and templates of partials that are used in the template. It may
|
||||
* also be a function that is used to load partial templates on the fly
|
||||
* that takes a single argument: the name of the partial.
|
||||
*
|
||||
* If the optional `tags` argument is given here it must be an array with two
|
||||
* string values: the opening and closing tags used in the template (e.g.
|
||||
* [ "<%", "%>" ]). The default is to mustache.tags.
|
||||
*/
|
||||
Writer.prototype.render = function render (template, view, partials, tags) {
|
||||
var tokens = this.parse(template, tags);
|
||||
var context = (view instanceof Context) ? view : new Context(view, undefined);
|
||||
return this.renderTokens(tokens, context, partials, template, tags);
|
||||
};
|
||||
|
||||
/**
|
||||
* Low-level method that renders the given array of `tokens` using
|
||||
* the given `context` and `partials`.
|
||||
*
|
||||
* Note: The `originalTemplate` is only ever used to extract the portion
|
||||
* of the original template that was contained in a higher-order section.
|
||||
* If the template doesn't use higher-order sections, this argument may
|
||||
* be omitted.
|
||||
*/
|
||||
Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, tags) {
|
||||
var buffer = '';
|
||||
|
||||
var token, symbol, value;
|
||||
for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
|
||||
value = undefined;
|
||||
token = tokens[i];
|
||||
symbol = token[0];
|
||||
|
||||
if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate);
|
||||
else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate);
|
||||
else if (symbol === '>') value = this.renderPartial(token, context, partials, tags);
|
||||
else if (symbol === '&') value = this.unescapedValue(token, context);
|
||||
else if (symbol === 'name') value = this.escapedValue(token, context);
|
||||
else if (symbol === 'text') value = this.rawValue(token);
|
||||
|
||||
if (value !== undefined)
|
||||
buffer += value;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
};
|
||||
|
||||
Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate) {
|
||||
var self = this;
|
||||
var buffer = '';
|
||||
var value = context.lookup(token[1]);
|
||||
|
||||
// This function is used to render an arbitrary template
|
||||
// in the current context by higher-order sections.
|
||||
function subRender (template) {
|
||||
return self.render(template, context, partials);
|
||||
}
|
||||
|
||||
if (!value) return;
|
||||
|
||||
if (isArray(value)) {
|
||||
for (var j = 0, valueLength = value.length; j < valueLength; ++j) {
|
||||
buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
|
||||
}
|
||||
} else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') {
|
||||
buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);
|
||||
} else if (isFunction(value)) {
|
||||
if (typeof originalTemplate !== 'string')
|
||||
throw new Error('Cannot use higher-order sections without the original template');
|
||||
|
||||
// Extract the portion of the original template that the section contains.
|
||||
value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);
|
||||
|
||||
if (value != null)
|
||||
buffer += value;
|
||||
} else {
|
||||
buffer += this.renderTokens(token[4], context, partials, originalTemplate);
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
|
||||
Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate) {
|
||||
var value = context.lookup(token[1]);
|
||||
|
||||
// Use JavaScript's definition of falsy. Include empty arrays.
|
||||
// See https://github.com/janl/mustache.js/issues/186
|
||||
if (!value || (isArray(value) && value.length === 0))
|
||||
return this.renderTokens(token[4], context, partials, originalTemplate);
|
||||
};
|
||||
|
||||
Writer.prototype.indentPartial = function indentPartial (partial, indentation, lineHasNonSpace) {
|
||||
var filteredIndentation = indentation.replace(/[^ \t]/g, '');
|
||||
var partialByNl = partial.split('\n');
|
||||
for (var i = 0; i < partialByNl.length; i++) {
|
||||
if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) {
|
||||
partialByNl[i] = filteredIndentation + partialByNl[i];
|
||||
}
|
||||
}
|
||||
return partialByNl.join('\n');
|
||||
};
|
||||
|
||||
Writer.prototype.renderPartial = function renderPartial (token, context, partials, tags) {
|
||||
if (!partials) return;
|
||||
|
||||
var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
|
||||
if (value != null) {
|
||||
var lineHasNonSpace = token[6];
|
||||
var tagIndex = token[5];
|
||||
var indentation = token[4];
|
||||
var indentedValue = value;
|
||||
if (tagIndex == 0 && indentation) {
|
||||
indentedValue = this.indentPartial(value, indentation, lineHasNonSpace);
|
||||
}
|
||||
return this.renderTokens(this.parse(indentedValue, tags), context, partials, indentedValue, tags);
|
||||
}
|
||||
};
|
||||
|
||||
Writer.prototype.unescapedValue = function unescapedValue (token, context) {
|
||||
var value = context.lookup(token[1]);
|
||||
if (value != null)
|
||||
return value;
|
||||
};
|
||||
|
||||
Writer.prototype.escapedValue = function escapedValue (token, context) {
|
||||
var value = context.lookup(token[1]);
|
||||
if (value != null)
|
||||
return typeof value === 'number' ? String(value) : mustache.escape(value);
|
||||
};
|
||||
|
||||
Writer.prototype.rawValue = function rawValue (token) {
|
||||
return token[1];
|
||||
};
|
||||
|
||||
var mustache = {
|
||||
name: 'mustache.js',
|
||||
version: '4.0.1',
|
||||
tags: [ '{{', '}}' ],
|
||||
clearCache: undefined,
|
||||
escape: undefined,
|
||||
parse: undefined,
|
||||
render: undefined,
|
||||
Scanner: undefined,
|
||||
Context: undefined,
|
||||
Writer: undefined,
|
||||
/**
|
||||
* Allows a user to override the default caching strategy, by providing an
|
||||
* object with set, get and clear methods. This can also be used to disable
|
||||
* the cache by setting it to the literal `undefined`.
|
||||
*/
|
||||
set templateCache (cache) {
|
||||
defaultWriter.templateCache = cache;
|
||||
},
|
||||
/**
|
||||
* Gets the default or overridden caching object from the default writer.
|
||||
*/
|
||||
get templateCache () {
|
||||
return defaultWriter.templateCache;
|
||||
}
|
||||
};
|
||||
|
||||
// All high-level mustache.* functions use this writer.
|
||||
var defaultWriter = new Writer();
|
||||
|
||||
/**
|
||||
* Clears all cached templates in the default writer.
|
||||
*/
|
||||
mustache.clearCache = function clearCache () {
|
||||
return defaultWriter.clearCache();
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses and caches the given template in the default writer and returns the
|
||||
* array of tokens it contains. Doing this ahead of time avoids the need to
|
||||
* parse templates on the fly as they are rendered.
|
||||
*/
|
||||
mustache.parse = function parse (template, tags) {
|
||||
return defaultWriter.parse(template, tags);
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the `template` with the given `view` and `partials` using the
|
||||
* default writer. If the optional `tags` argument is given here it must be an
|
||||
* array with two string values: the opening and closing tags used in the
|
||||
* template (e.g. [ "<%", "%>" ]). The default is to mustache.tags.
|
||||
*/
|
||||
mustache.render = function render (template, view, partials, tags) {
|
||||
if (typeof template !== 'string') {
|
||||
throw new TypeError('Invalid template! Template should be a "string" ' +
|
||||
'but "' + typeStr(template) + '" was given as the first ' +
|
||||
'argument for mustache#render(template, view, partials)');
|
||||
}
|
||||
|
||||
return defaultWriter.render(template, view, partials, tags);
|
||||
};
|
||||
|
||||
// Export the escaping function so that the user may override it.
|
||||
// See https://github.com/janl/mustache.js/issues/244
|
||||
mustache.escape = escapeHtml;
|
||||
|
||||
// Export these mainly for testing, but also for advanced usage.
|
||||
mustache.Scanner = Scanner;
|
||||
mustache.Context = Context;
|
||||
mustache.Writer = Writer;
|
||||
|
||||
return mustache;
|
||||
|
||||
})));
|
110
www/test/0.1.1/test/manual/browser-only-tests.html
Normal file
110
www/test/0.1.1/test/manual/browser-only-tests.html
Normal file
@ -0,0 +1,110 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Mocha Tests</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="../../node_modules/mocha/mocha.css" />
|
||||
</head>
|
||||
<body style="padding:20px;font-family: sans-serif">
|
||||
<div id="mocha"></div>
|
||||
<script src="../../node_modules/chai/chai.js"></script>
|
||||
<script src="../../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../../node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="../../src/htmx.js"></script>
|
||||
<script class="mocha-init">
|
||||
mocha.setup('bdd');
|
||||
mocha.checkLeaks();
|
||||
should = chai.should();
|
||||
</script>
|
||||
|
||||
<script src="../util/util.js"></script>
|
||||
<script>
|
||||
describe("Browser Only Tests", function() {
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it("should handle a basic back button click", function (done) {
|
||||
this.server.respondWith("GET", "/test", "second");
|
||||
|
||||
getWorkArea().innerHTML.should.be.equal("");
|
||||
var div = make('<div hx-push-url="true" hx-get="/test">first</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("second")
|
||||
history.back();
|
||||
setTimeout(function(){
|
||||
getWorkArea().textContent.should.equal("first");
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
it("should handle two forward clicks then back twice", function (done) {
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, "" + i);
|
||||
});
|
||||
|
||||
getWorkArea().innerHTML.should.equal("");
|
||||
var div = make('<div hx-push-url="true" hx-get="/test" class="">0</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("1")
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("2")
|
||||
|
||||
history.back();
|
||||
setTimeout(function(){
|
||||
getWorkArea().textContent.should.equal("1");
|
||||
history.back();
|
||||
setTimeout(function(){
|
||||
getWorkArea().textContent.should.equal("0");
|
||||
done();
|
||||
}, 20);
|
||||
}, 20);
|
||||
})
|
||||
|
||||
it("should handle a back, forward, back button click", function (done) {
|
||||
this.server.respondWith("GET", "/test", "second");
|
||||
|
||||
getWorkArea().innerHTML.should.equal("");
|
||||
var div = make('<div hx-push-url="true" hx-get="/test" class="">first</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("second")
|
||||
history.back();
|
||||
setTimeout(function(){
|
||||
getWorkArea().textContent.should.equal("first");
|
||||
history.forward();
|
||||
setTimeout(function() {
|
||||
getWorkArea().textContent.should.equal("second");
|
||||
history.back();
|
||||
setTimeout(function() {
|
||||
getWorkArea().textContent.should.equal("first");
|
||||
done();
|
||||
}, 20);
|
||||
}, 20);
|
||||
}, 20);
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script class="mocha-exec">
|
||||
mocha.run();
|
||||
</script>
|
||||
<em>Work Area</em>
|
||||
<hr/>
|
||||
<div id="work-area" hx-history-elt>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
28
www/test/0.1.1/test/manual/confirm-and-prompt.html
Normal file
28
www/test/0.1.1/test/manual/confirm-and-prompt.html
Normal file
@ -0,0 +1,28 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Test if indicators are invisible by default</title>
|
||||
<script src="../../src/htmx.js"></script>
|
||||
</head>
|
||||
<body style="padding:20px;font-family: sans-serif">
|
||||
<script src="../../node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="../../src/htmx.js"></script>
|
||||
<script src="../util/util.js"></script>
|
||||
<script>
|
||||
server = makeServer();
|
||||
server.autoRespond = true;
|
||||
server.respondWith("GET", "/prompt", function(xhr){
|
||||
xhr.respond(200, {}, "You entered: " + xhr.requestHeaders["HX-Prompt"]);
|
||||
})
|
||||
server.respondWith("GET", "/confirm", function(xhr){
|
||||
xhr.respond(200, {}, "Confirmed")
|
||||
})
|
||||
</script>
|
||||
<h1>Prompt & Confirm Tests</h1>
|
||||
<button hx-get="/prompt" hx-prompt="Enter some text and it should be echoed in this button">Click For Prompt</button>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<button hx-get="/confirm" hx-confirm="Confirm The Action">Click For Confirm</button>
|
||||
</body>
|
||||
</html>
|
16
www/test/0.1.1/test/manual/no-indicator-css.html
Normal file
16
www/test/0.1.1/test/manual/no-indicator-css.html
Normal file
@ -0,0 +1,16 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="htmx-config" content='{"includeIndicatorStyles":false}'>
|
||||
<title>Test if the includeIndicatorStyles meta option works</title>
|
||||
<script src="../../src/htmx.js"></script>
|
||||
</head>
|
||||
<body style="padding:20px;font-family: sans-serif">
|
||||
<h1>You should see bars here:</h1>
|
||||
<p>
|
||||
We are overriding the normal CSS inclusion with the meta directive <code>{"includeIndicatorStyles":false}</code>
|
||||
so you should see the indicator because it is not being hidden by the default classes.
|
||||
</p>
|
||||
<img class="htmx-indicator" src="../img/bars.svg" width="200">
|
||||
</body>
|
||||
</html>
|
107
www/test/0.1.1/test/manual/scroll-test-eventHandler.html
Normal file
107
www/test/0.1.1/test/manual/scroll-test-eventHandler.html
Normal file
@ -0,0 +1,107 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Test Scroll Event Handler</title>
|
||||
<script src="../../node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="../util/util.js"></script>
|
||||
<script src="../../src/htmx.js"></script>
|
||||
|
||||
<script>
|
||||
server = makeServer();
|
||||
server.autoRespond = true;
|
||||
server.respondWith("GET", "/more_content", "Here is more content for this page, loaded 'remotely'.");
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.panel {
|
||||
height:200px;
|
||||
background-color:#eee;
|
||||
margin-bottom:20px;
|
||||
padding:20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body style="padding:20px;font-family: sans-serif">
|
||||
<h1>Scrolling Event Handler Tests</h1>
|
||||
<p>You should be able to scroll this page at any speed and see HTML fragments loaded into the DIVs "remotely" without any hiccups.</p>
|
||||
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
<div class="panel" hx-get="/more_content" hx-trigger="revealed"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
29
www/test/0.1.1/test/manual/scroll-test-startEnd.html
Normal file
29
www/test/0.1.1/test/manual/scroll-test-startEnd.html
Normal file
@ -0,0 +1,29 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Test Scroll Behavior</title>
|
||||
<script src="../../src/htmx.js"></script>
|
||||
</head>
|
||||
<body style="padding:20px;font-family: sans-serif">
|
||||
<script src="../../node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="../../src/htmx.js"></script>
|
||||
<script src="../util/util.js"></script>
|
||||
<script>
|
||||
server = makeServer();
|
||||
server.autoRespond = true;
|
||||
server.respondWith("GET", "/more_divs", "<div>More Content</div>");
|
||||
</script>
|
||||
<h1>Scrolling Start/End Tests</h1>
|
||||
|
||||
<h3>End</h3>
|
||||
<div hx-get="/more_divs" hx-swap="beforeend scroll:bottom" style="height: 100px; overflow: scroll">
|
||||
Click To Add Content...
|
||||
</div>
|
||||
<hr/>
|
||||
<h3>Start</h3>
|
||||
<div hx-get="/more_divs" hx-swap="beforeend scroll:top" style="height: 100px; overflow: scroll">
|
||||
Click To Add Content...
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
41
www/test/0.1.1/test/manual/sse-multichannel.html
Normal file
41
www/test/0.1.1/test/manual/sse-multichannel.html
Normal file
@ -0,0 +1,41 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="../../src/htmx.js"></script>
|
||||
<script>
|
||||
htmx.createEventSource = function(url){
|
||||
return new EventSource(url, {withCredentials:false})
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
*{
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 10px;
|
||||
border: solid 1px gray;
|
||||
margin-bottom: 20px;
|
||||
background-color:white;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight:bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="page" hx-sse="connect:http://sseplaceholder.openfollow.org/posts.html?types=Event1%2cEvent2%2cEvent3%2cEvent4">
|
||||
<h3>Multiple Listeners. message only</h3>
|
||||
<div class="container" hx-sse="swap:Event1">Waiting for Posts in Event1 channel...</div>
|
||||
<div class="container" hx-sse="swap:Event2">Waiting for Posts in Event2 channel...</div>
|
||||
<div class="container" hx-sse="swap:Event3">Waiting for Posts in Event3 channel...</div>
|
||||
<div class="container" hx-sse="swap:Event4">Waiting for Posts in Event4 channel...</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
49
www/test/0.1.1/test/manual/sse-settle.html
Normal file
49
www/test/0.1.1/test/manual/sse-settle.html
Normal file
@ -0,0 +1,49 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="../../src/htmx.js"></script>
|
||||
<script>
|
||||
htmx.createEventSource = function(url){
|
||||
return new EventSource(url, {withCredentials:false})
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
*{
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 10px;
|
||||
border: solid 1px gray;
|
||||
margin-bottom: 20px;
|
||||
background-color:white;
|
||||
}
|
||||
|
||||
.htmx-settling {
|
||||
border:solid 3px red!important;
|
||||
padding:8px!important;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight:bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="page">
|
||||
<h3>Settling Options</h3>
|
||||
<div hx-sse="connect:http://sseplaceholder.openfollow.org/comments.html">
|
||||
<div class="container" hx-sse="swap:message" hx-swap="innerHTML settle:100ms">Waiting for Comments...</div>
|
||||
<div class="container" hx-sse="swap:message" hx-swap="innerHTML settle:200ms">Waiting for Comments...</div>
|
||||
<div class="container" hx-sse="swap:message" hx-swap="innerHTML settle:300ms">Waiting for Comments...</div>
|
||||
<div class="container" hx-sse="swap:message" hx-swap="innerHTML settle:400ms">Waiting for Comments...</div>
|
||||
<div class="container" hx-sse="swap:message" hx-swap="innerHTML settle:500ms">Waiting for Comments...</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
43
www/test/0.1.1/test/manual/sse.html
Normal file
43
www/test/0.1.1/test/manual/sse.html
Normal file
@ -0,0 +1,43 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="../../src/htmx.js"></script>
|
||||
<script>
|
||||
// "withCredentials:false" is necessary to circumvent CORS restrictions
|
||||
htmx.createEventSource = function(url){
|
||||
return new EventSource(url, {withCredentials:false})
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
*{
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 10px;
|
||||
border: solid 1px gray;
|
||||
margin-bottom: 20px;
|
||||
background-color:white;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight:bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="page">
|
||||
<h3>Multiple Listeners. message only</h3>
|
||||
<div class="container" hx-sse="connect:http://sseplaceholder.openfollow.org/posts.html swap:message">Waiting for Posts...</div>
|
||||
<div class="container" hx-sse="connect:http://sseplaceholder.openfollow.org/comments.html swap:message">Waiting for Comments...</div>
|
||||
<div class="container" hx-sse="connect:http://sseplaceholder.openfollow.org/albums.html swap:message">Waiting for Albums...</div>
|
||||
<div class="container" hx-sse="connect:http://sseplaceholder.openfollow.org/todos.html swap:message">Waiting for ToDos...</div>
|
||||
<div class="container" hx-sse="connect:http://sseplaceholder.openfollow.org/users.html swap:message">Waiting for Users...</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
11
www/test/0.1.1/test/manual/yes-indicator-css.html
Normal file
11
www/test/0.1.1/test/manual/yes-indicator-css.html
Normal file
@ -0,0 +1,11 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Test if indicators are invisible by default</title>
|
||||
<script src="../../src/htmx.js"></script>
|
||||
</head>
|
||||
<body style="padding:20px;font-family: sans-serif">
|
||||
<h1>You should not see bars here:</h1>
|
||||
<img class="htmx-indicator" src="../img/bars.svg" width="200">
|
||||
</body>
|
||||
</html>
|
56
www/test/0.1.1/test/scratch.html
Normal file
56
www/test/0.1.1/test/scratch.html
Normal file
@ -0,0 +1,56 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<style>
|
||||
div {
|
||||
transition: all 1000ms ease-in;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.hx-show-indicator .indicator {
|
||||
opacity: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
|
||||
|
||||
</head>
|
||||
<body style="padding:20px;font-family: sans-serif">
|
||||
<script src="../node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="../src/htmx.js"></script>
|
||||
<script src="util/util.js"></script>
|
||||
<script src="util/scratch_server.js"></script>
|
||||
|
||||
<script>
|
||||
// this.server.respondWith("GET", "/test", '<a hx-get="/test2">Click Me</a>');
|
||||
// this.server.respondWith("GET", "/test2", "Clicked!");
|
||||
//
|
||||
// make('<div hx-get="/test">dd</div>')
|
||||
|
||||
htmx.logAll();
|
||||
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.respond(201, {}, '<form><input hx-trigger="keyup delay:1s changed" hx-swap="outerHTML" hx-get="/test" id="i1" value="blahblah"/></form>')
|
||||
});
|
||||
|
||||
make('<form hx-target="this"><input hx-trigger="keyup delay:1s changed" hx-swap="outerHTML" hx-get="/test" id="i1"/></form>');
|
||||
</script>
|
||||
|
||||
|
||||
<h2>Server Options</h2>
|
||||
<button onclick="server.respond()">Server Respond</button>
|
||||
<br/>
|
||||
Autorespond: <input id="autorespond" type="checkbox" onclick="toggleAutoRespond()">
|
||||
<br/>
|
||||
<br/>
|
||||
<em>Work Area</em>
|
||||
<hr/>
|
||||
|
||||
<div id="work-area" hx-history-elt>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
17
www/test/0.1.1/test/util/scratch_server.js
Normal file
17
www/test/0.1.1/test/util/scratch_server.js
Normal file
@ -0,0 +1,17 @@
|
||||
var server = makeServer();
|
||||
var autoRespond = localStorage.getItem('hx-scratch-autorespond') == "true";
|
||||
server.autoRespond = autoRespond;
|
||||
ready(function () {
|
||||
if (autoRespond) {
|
||||
byId("autorespond").setAttribute("checked", "true");
|
||||
}
|
||||
})
|
||||
function toggleAutoRespond() {
|
||||
if (server.autoRespond) {
|
||||
localStorage.removeItem('hx-scratch-autorespond');
|
||||
server.autoRespond = false;
|
||||
} else {
|
||||
localStorage.setItem('hx-scratch-autorespond', 'true');
|
||||
server.autoRespond = true;
|
||||
}
|
||||
}
|
101
www/test/0.1.1/test/util/util.js
Normal file
101
www/test/0.1.1/test/util/util.js
Normal file
@ -0,0 +1,101 @@
|
||||
/* Test Utilities */
|
||||
|
||||
function byId(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function make(htmlStr) {
|
||||
var makeFn = function(){
|
||||
var range = document.createRange();
|
||||
var fragment = range.createContextualFragment(htmlStr);
|
||||
var wa = getWorkArea();
|
||||
for (var i = fragment.childNodes.length - 1; i >= 0; i--) {
|
||||
var child = fragment.childNodes[i];
|
||||
htmx.process(child);
|
||||
wa.appendChild(child);
|
||||
}
|
||||
return wa.lastChild;
|
||||
}
|
||||
if (getWorkArea()) {
|
||||
return makeFn();
|
||||
} else {
|
||||
ready(makeFn);
|
||||
}
|
||||
}
|
||||
|
||||
function ready(fn) {
|
||||
if (document.readyState !== 'loading') {
|
||||
fn();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
}
|
||||
|
||||
function getWorkArea() {
|
||||
return byId("work-area");
|
||||
}
|
||||
|
||||
function clearWorkArea() {
|
||||
getWorkArea().innerHTML = "";
|
||||
}
|
||||
|
||||
function removeWhiteSpace(str) {
|
||||
return str.replace(/\s/g, "");
|
||||
}
|
||||
|
||||
function getHTTPMethod(xhr) {
|
||||
return xhr.requestHeaders['X-HTTP-Method-Override'] || xhr.method;
|
||||
}
|
||||
|
||||
function makeServer(){
|
||||
var server = sinon.fakeServer.create();
|
||||
server.fakeHTTPMethods = true;
|
||||
server.getHTTPMethod = function(xhr) {
|
||||
return getHTTPMethod(xhr);
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
function parseParams(str) {
|
||||
var re = /([^&=]+)=?([^&]*)/g;
|
||||
var decode = function (str) {
|
||||
return decodeURIComponent(str.replace(/\+/g, ' '));
|
||||
};
|
||||
var params = {}, e;
|
||||
if (str) {
|
||||
if (str.substr(0, 1) == '?') {
|
||||
str = str.substr(1);
|
||||
}
|
||||
while (e = re.exec(str)) {
|
||||
var k = decode(e[1]);
|
||||
var v = decode(e[2]);
|
||||
if (params[k] !== undefined) {
|
||||
if (!Array.isArray(params[k])) {
|
||||
params[k] = [params[k]];
|
||||
}
|
||||
params[k].push(v);
|
||||
} else {
|
||||
params[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
function getQuery(url) {
|
||||
var question = url.indexOf("?");
|
||||
var hash = url.indexOf("#");
|
||||
if(hash==-1 && question==-1) return "";
|
||||
if(hash==-1) hash = url.length;
|
||||
return question==-1 || hash==question+1 ? url.substring(hash) :
|
||||
url.substring(question+1,hash);
|
||||
}
|
||||
|
||||
function getParameters(xhr) {
|
||||
if (getHTTPMethod(xhr) == "GET") {
|
||||
return parseParams(getQuery(xhr.url));
|
||||
} else {
|
||||
return parseParams(xhr.requestBody);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
<html><body style='font-family: sans-serif'><h1>HTMX TESTS</h1><ul>
|
||||
<li><a href='/test/0.1.1/test'>0.1.1</a>
|
||||
<li><a href='/test/0.1.0/test'>0.1.0</a>
|
||||
<li><a href='/test/0.0.8/test'>0.0.8</a>
|
||||
<li><a href='/test/0.0.7/test'>0.0.7</a>
|
||||
|
Loading…
x
Reference in New Issue
Block a user