diff --git a/d3/d3-tests.ts b/d3/d3-tests.ts index 36ac355fe..bf500d409 100644 --- a/d3/d3-tests.ts +++ b/d3/d3-tests.ts @@ -1,129 +1,615 @@ -/// - -d3.selectAll("p").style("color", "white"); -d3.select("body").style("background-color", "black"); -d3.selectAll("p").style("color", function () { - return "hsl(" + Math.random() * 360 + ",100%,50%)"; -}); -d3.selectAll("p").style("color", function (d, i) { - return i % 2 ? "#fff" : "#eee"; -}); -d3.selectAll("p") - .data([4, 8, 15, 16, 23, 42]) - .style("font-size", function (d) { return d + "px"; }); -d3.select("body").selectAll("p") - .data([4, 8, 15, 16, 23, 42]) - .enter().append("p") - .text(function (d) { return "I’m number " + d + "!"; }); -var p = d3.select("body").selectAll("p") - .data([4, 8, 15, 16, 23, 42]) - .text(String); -p.enter().append("p") - .text(String); -p.exit().remove(); -d3.select("body").transition() - .style("background-color", "black"); -d3.selectAll("circle").transition() - .duration(750) - .delay(function (d, i) { return i * 10; }) - .attr("r", function (d) { return Math.sqrt(d * scale); }); - -function testOrdinalScale() { - var x = d3.scale.ordinal().range(["foo", "bar"]); - x.domain([0, 1]); - var result = x(0); - - var x = d3.scale.ordinal().domain(["a", "b", "c"]).rangePoints([120, 0]); - var x = d3.scale.ordinal().domain(["a", "b", "c"]).rangePoints([120, 0], 1); - - var x = d3.scale.ordinal().domain(["a", "b", "c"]).rangeBands([120, 0]); - var x = d3.scale.ordinal().domain(["a", "b", "c"]).rangeBands([120, 0], .2); - var x = d3.scale.ordinal().domain(["a", "b", "c"]).rangeBands([120, 0], .2, .1); - - var x = d3.scale.ordinal().domain(["a", "b", "c"]).rangeRoundBands([0, 100]); - var x = d3.scale.ordinal().domain(["a", "b", "c"]).rangeRoundBands([0, 100], .2); - var x = d3.scale.ordinal().domain(["a", "b", "c"]).rangeRoundBands([120, 0], .2, .1); -} - - -function testKeys() { - var x = d3.keys({ a: 1, b: 1 }); - - function abc() { - this.a = 1; - this.b = 2; - } - var x = d3.keys(new abc()); -} - -function testSVGArc() { - var a = d3.svg.arc().innerRadius(100).outerRadius(200); - var a = d3.svg.arc().outerRadius(100).startAngle(0).endAngle(Math.PI); - - var f = function () => { - return 42; - } - var a = d3.svg.arc().innerRadius(0).outerRadius(f).startAngle(f).endAngle(f() * 2) - - var str = a(); - var str = a({ outerRadius: 50 }); - var str = a.outerRadius(100)(); - var str = a.endAngle(Math.PI / 2)() - var str = a({ startAngle: Math.PI / 2 }); - - var c = d3.svg.arc().innerRadius(0).outerRadius(100).startAngle(0).endAngle(2 * Math.PI).centroid(); - var num = c[0]; -} - -function testPieLayout() { - var p = d3.layout.pie().sort(null).value(function (d) { return d.value; }); - var data = [1, 2, 3, 4]; - var arcs = p(data); -} - -//Example from http://bl.ocks.org/3887235 -function testPieChart() { - var width = 960, - height = 500, - radius = Math.min(width, height) / 2; - - var color = d3.scale.ordinal() - .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); - - var arc = d3.svg.arc() - .outerRadius(radius - 10) - .innerRadius(0); - - var pie = d3.layout.pie() - .sort(null) - .value(function (d) { return d.population; }); - - var svg = d3.select("body").append("svg") - .attr("width", width) - .attr("height", height) - .append("g") - .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); - - d3.csv("data.csv", function (error, data) { - - data.forEach(function (d) { - d.population = +d.population; - }); - - var g = svg.selectAll(".arc") - .data(pie(data)) - .enter().append("g") - .attr("class", "arc"); - - g.append("path") - .attr("d", arc) - .style("fill", function (d) { return color(d.data.age); }); - - g.append("text") - .attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; }) - .attr("dy", ".35em") - .style("text-anchor", "middle") - .text(function (d) { return d.data.age; }); - - }); -} +/// + +//Example from http://bl.ocks.org/3887235 +function testPieChart() { + var width = 960, + height = 500, + radius = Math.min(width, height) / 2; + + var color = d3.scale.ordinal() + .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); + + var arc = d3.svg.arc() + .outerRadius(radius - 10) + .innerRadius(0); + + var pie = d3.layout.pie() + .sort(null) + .value(function (d) { return d.population; }); + + var svg = d3.select("body").append("svg") + .attr("width", width) + .attr("height", height) + .append("g") + .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); + + d3.csv("data.csv", function (error, data) { + + data.forEach(function (d) { + d.population = +d.population; + }); + + var g = svg.selectAll(".arc") + .data(pie(data)) + .enter().append("g") + .attr("class", "arc"); + + g.append("path") + .attr("d", arc) + .style("fill", function (d) { return color(d.data.age); }); + + g.append("text") + .attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; }) + .attr("dy", ".35em") + .style("text-anchor", "middle") + .text(function (d) { return d.data.age; }); + + }); +} + +//Example from http://bl.ocks.org/3887051 +function groupedBarChart() => { + var margin = { top: 20, right: 20, bottom: 30, left: 40 }, + width = 960 - margin.left - margin.right, + height = 500 - margin.top - margin.bottom; + + var x0 = d3.scale.ordinal() + .rangeRoundBands([0, width], .1); + + var x1 = d3.scale.ordinal(); + + var y = d3.scale.linear() + .range([height, 0]); + + var color = d3.scale.ordinal() + .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); + + var xAxis = d3.svg.axis() + .scale(x0) + .orient("bottom"); + + var yAxis = d3.svg.axis() + .scale(y) + .orient("left") + .tickFormat(d3.format(".2s")); + + var svg = d3.select("body").append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + d3.csv("data.csv", function (error, data) { + var ageNames = d3.keys(data[0]).filter(function (key) { return key !== "State"; }); + + data.forEach(function (d) { + d.ages = ageNames.map(function (name) { return { name: name, value: +d[name] }; }); + }); + + x0.domain(data.map(function (d) { return d.State; })); + x1.domain(ageNames).rangeRoundBands([0, x0.rangeBand()]); + y.domain([0, d3.max(data, function (d) { return d3.max(d.ages, function (d) { return d.value; }); })]); + + svg.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") + .call(xAxis); + + svg.append("g") + .attr("class", "y axis") + .call(yAxis) + .append("text") + .attr("transform", "rotate(-90)") + .attr("y", 6) + .attr("dy", ".71em") + .style("text-anchor", "end") + .text("Population"); + + var state = svg.selectAll(".state") + .data(data) + .enter().append("g") + .attr("class", "g") + .attr("transform", function (d) { return "translate(" + x0(d.State) + ",0)"; }); + + state.selectAll("rect") + .data(function (d) { return d.ages; }) + .enter().append("rect") + .attr("width", x1.rangeBand()) + .attr("x", function (d) { return x1(d.name); }) + .attr("y", function (d) { return y(d.value); }) + .attr("height", function (d) { return height - y(d.value); }) + .style("fill", function (d) { return color(d.name); }); + + var legend = svg.selectAll(".legend") + .data(ageNames.reverse()) + .enter().append("g") + .attr("class", "legend") + .attr("transform", function (d, i) { return "translate(0," + i * 20 + ")"; }); + + legend.append("rect") + .attr("x", width - 18) + .attr("width", 18) + .attr("height", 18) + .style("fill", color); + + legend.append("text") + .attr("x", width - 24) + .attr("y", 9) + .attr("dy", ".35em") + .style("text-anchor", "end") + .text(function (d) { return d; }); + + }); +} + +//Example from http://bl.ocks.org/3886208 +function stackedBarChart() { + var margin = { top: 20, right: 20, bottom: 30, left: 40 }, + width = 960 - margin.left - margin.right, + height = 500 - margin.top - margin.bottom; + + var x = d3.scale.ordinal() + .rangeRoundBands([0, width], .1); + + var y = d3.scale.linear() + .rangeRound([height, 0]); + + var color = d3.scale.ordinal() + .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); + + var xAxis = d3.svg.axis() + .scale(x) + .orient("bottom"); + + var yAxis = d3.svg.axis() + .scale(y) + .orient("left") + .tickFormat(d3.format(".2s")); + + var svg = d3.select("body").append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + d3.csv("data.csv", function (error, data) { + color.domain(d3.keys(data[0]).filter(function (key) { return key !== "State"; })); + + data.forEach(function (d) { + var y0 = 0; + d.ages = color.domain().map(function (name) { return { name: name, y0: y0, y1: y0 += +d[name] }; }); + d.total = d.ages[d.ages.length - 1].y1; + }); + + data.sort(function (a, b) { return b.total - a.total; }); + + x.domain(data.map(function (d) { return d.State; })); + y.domain([0, d3.max(data, function (d) { return d.total; })]); + + svg.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") + .call(xAxis); + + svg.append("g") + .attr("class", "y axis") + .call(yAxis) + .append("text") + .attr("transform", "rotate(-90)") + .attr("y", 6) + .attr("dy", ".71em") + .style("text-anchor", "end") + .text("Population"); + + var state = svg.selectAll(".state") + .data(data) + .enter().append("g") + .attr("class", "g") + .attr("transform", function (d) { return "translate(" + x(d.State) + ",0)"; }); + + state.selectAll("rect") + .data(function (d) { return d.ages; }) + .enter().append("rect") + .attr("width", x.rangeBand()) + .attr("y", function (d) { return y(d.y1); }) + .attr("height", function (d) { return y(d.y0) - y(d.y1); }) + .style("fill", function (d) { return color(d.name); }); + + var legend = svg.selectAll(".legend") + .data(color.domain().reverse()) + .enter().append("g") + .attr("class", "legend") + .attr("transform", function (d, i) { return "translate(0," + i * 20 + ")"; }); + + legend.append("rect") + .attr("x", width - 18) + .attr("width", 18) + .attr("height", 18) + .style("fill", color); + + legend.append("text") + .attr("x", width - 24) + .attr("y", 9) + .attr("dy", ".35em") + .style("text-anchor", "end") + .text(function (d) { return d; }); + + }); +} + +// example from http://bl.ocks.org/3886394 +function normalizedBarChart() { + var margin = { top: 20, right: 100, bottom: 30, left: 40 }, + width = 960 - margin.left - margin.right, + height = 500 - margin.top - margin.bottom; + + var x = d3.scale.ordinal() + .rangeRoundBands([0, width], .1); + + var y = d3.scale.linear() + .rangeRound([height, 0]); + + var color = d3.scale.ordinal() + .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); + + var xAxis = d3.svg.axis() + .scale(x) + .orient("bottom"); + + var yAxis = d3.svg.axis() + .scale(y) + .orient("left") + .tickFormat(d3.format(".0%")); + + var svg = d3.select("body").append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + d3.csv("data.csv", function (error, data) { + color.domain(d3.keys(data[0]).filter(function (key) { return key !== "State"; })); + + data.forEach(function (d) { + var y0 = 0; + d.ages = color.domain().map(function (name) { return { name: name, y0: y0, y1: y0 += +d[name] }; }); + d.ages.forEach(function (d) { d.y0 /= y0; d.y1 /= y0; }); + }); + + data.sort(function (a, b) { return b.ages[0].y1 - a.ages[0].y1; }); + + x.domain(data.map(function (d) { return d.State; })); + + svg.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") + .call(xAxis); + + svg.append("g") + .attr("class", "y axis") + .call(yAxis); + + var state = svg.selectAll(".state") + .data(data) + .enter().append("g") + .attr("class", "state") + .attr("transform", function (d) { return "translate(" + x(d.State) + ",0)"; }); + + state.selectAll("rect") + .data(function (d) { return d.ages; }) + .enter().append("rect") + .attr("width", x.rangeBand()) + .attr("y", function (d) { return y(d.y1); }) + .attr("height", function (d) { return y(d.y0) - y(d.y1); }) + .style("fill", function (d) { return color(d.name); }); + + var legend = svg.select(".state:last-child").selectAll(".legend") + .data(function (d) { return d.ages; }) + .enter().append("g") + .attr("class", "legend") + .attr("transform", function (d) { return "translate(" + x.rangeBand() / 2 + "," + y((d.y0 + d.y1) / 2) + ")"; }); + + legend.append("line") + .attr("x2", 10); + + legend.append("text") + .attr("x", 13) + .attr("dy", ".35em") + .text(function (d) { return d.name; }); + + }); +} + +// example from http://bl.ocks.org/3885705 +function sortablebarChart() { + var margin = { top: 20, right: 20, bottom: 30, left: 40 }, + width = 960 - margin.left - margin.right, + height = 500 - margin.top - margin.bottom; + + var formatPercent = d3.format(".0%"); + + var x = d3.scale.ordinal() + .rangeRoundBands([0, width], .1, 1); + + var y = d3.scale.linear() + .range([height, 0]); + + var xAxis = d3.svg.axis() + .scale(x) + .orient("bottom"); + + var yAxis = d3.svg.axis() + .scale(y) + .orient("left") + .tickFormat(formatPercent); + + var svg = d3.select("body").append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + d3.tsv("data.tsv", function (error, data) { + + data.forEach(function (d) { + d.frequency = +d.frequency; + }); + + x.domain(data.map(function (d) { return d.letter; })); + y.domain([0, d3.max(data, function (d) { return d.frequency; })]); + + svg.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") + .call(xAxis); + + svg.append("g") + .attr("class", "y axis") + .call(yAxis) + .append("text") + .attr("transform", "rotate(-90)") + .attr("y", 6) + .attr("dy", ".71em") + .style("text-anchor", "end") + .text("Frequency"); + + svg.selectAll(".bar") + .data(data) + .enter().append("rect") + .attr("class", "bar") + .attr("x", function (d) { return x(d.letter); }) + .attr("width", x.rangeBand()) + .attr("y", function (d) { return y(d.frequency); }) + .attr("height", function (d) { return height - y(d.frequency); }); + + d3.select("input").on("change", change); + + var sortTimeout = setTimeout(function () { + d3.select("input").property("checked", true).each(change); + }, 2000); + + function change() { + clearTimeout(sortTimeout); + + var x0 = x.domain(data.sort(this.checked + ? function (a, b) { return b.frequency - a.frequency; } + : function (a, b) { return d3.ascending(a.letter, b.letter); }) + .map(function (d) { return d.letter; })) + .copy(); + + var transition = svg.transition().duration(750), + delay = function (d, i) { return i * 50; }; + + transition.selectAll(".bar") + .delay(delay) + .attr("x", function (d) { return x0(d.letter); }); + + transition.select(".x.axis") + .call(xAxis) + .selectAll("g") + .delay(delay); + } + }); +} + +//example from http://bl.ocks.org/4063318 +function callenderView() { + var width = 960, + height = 136, + cellSize = 17; // cell size + + var day = d3.time.format("%w"), + week = d3.time.format("%U"), + percent = d3.format(".1%"), + format = d3.time.format("%Y-%m-%d"); + + var color = d3.scale.quantize() + .domain([-.05, .05]) + .range(d3.range(11).map(function (d) { return "q" + d + "-11"; })); + + var svg = d3.select("body").selectAll("svg") + .data(d3.range(1990, 2011)) + .enter().append("svg") + .attr("width", width) + .attr("height", height) + .attr("class", "RdYlGn") + .append("g") + .attr("transform", "translate(" + ((width - cellSize * 53) / 2) + "," + (height - cellSize * 7 - 1) + ")"); + + svg.append("text") + .attr("transform", "translate(-6," + cellSize * 3.5 + ")rotate(-90)") + .style("text-anchor", "middle") + .text(function (d) { return d; }); + + var rect = svg.selectAll(".day") + .data(function (d) { return d3.time.days(new Date(d, 0, 1), new Date(d + 1, 0, 1)); }) + .enter().append("rect") + .attr("class", "day") + .attr("width", cellSize) + .attr("height", cellSize) + .attr("x", function (d) { return parseInt(week(d)) * cellSize; }) + .attr("y", function (d) { return parseInt(day(d)) * cellSize; }) + .datum(format); + + rect.append("title") + .text(function (d) { return d; }); + + svg.selectAll(".month") + .data(function (d) { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); }) + .enter().append("path") + .attr("class", "month") + .attr("d", monthPath); + + d3.csv("dji.csv", function (error, csv) { + var data = d3.nest() + .key(function (d) { return d.Date; }) + .rollup(function (d) { return (d[0].Close - d[0].Open) / d[0].Open; }) + .map(csv); + + rect.filter(function (d) { return d in data; }) + .attr("class", function (d) { return "day " + color(data[d]); }) + .select("title") + .text(function (d) { return d + ": " + percent(data[d]); }); + }); + + function monthPath(t0) { + var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0), + d0 = +day(t0), w0 = +week(t0), + d1 = +day(t1), w1 = +week(t1); + return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize + + "H" + w0 * cellSize + "V" + 7 * cellSize + + "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize + + "H" + (w1 + 1) * cellSize + "V" + 0 + + "H" + (w0 + 1) * cellSize + "Z"; + } + + d3.select(self.frameElement).style("height", "2910px"); +} + +// example from http://bl.ocks.org/3883245 +function lineChart { + var margin = { top: 20, right: 20, bottom: 30, left: 50 }, + width = 960 - margin.left - margin.right, + height = 500 - margin.top - margin.bottom; + + var parseDate = d3.time.format("%d-%b-%y").parse; + + var x = d3.time.scale() + .range([0, width]); + + var y = d3.scale.linear() + .range([height, 0]); + + var xAxis = d3.svg.axis() + .scale(x) + .orient("bottom"); + + var yAxis = d3.svg.axis() + .scale(y) + .orient("left"); + + var line = d3.svg.line() + .x(function (d) { return x(d.date); }) + .y(function (d) { return y(d.close); }); + + var svg = d3.select("body").append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + d3.tsv("data.tsv", function (error, data) { + data.forEach(function (d) { + d.date = parseDate(d.date); + d.close = +d.close; + }); + + x.domain(d3.extent(data, function (d) { return d.date; })); + y.domain(d3.extent(data, function (d) { return d.close; })); + + svg.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") + .call(xAxis); + + svg.append("g") + .attr("class", "y axis") + .call(yAxis) + .append("text") + .attr("transform", "rotate(-90)") + .attr("y", 6) + .attr("dy", ".71em") + .style("text-anchor", "end") + .text("Price ($)"); + + svg.append("path") + .datum(data) + .attr("class", "line") + .attr("d", line); + }); +} + +//example from http://bl.ocks.org/3884914 +function bivariateAreaChart { + var margin = { top: 20, right: 20, bottom: 30, left: 50 }, + width = 960 - margin.left - margin.right, + height = 500 - margin.top - margin.bottom; + + var parseDate = d3.time.format("%Y%m%d").parse; + + var x = d3.time.scale() + .range([0, width]); + + var y = d3.scale.linear() + .range([height, 0]); + + var xAxis = d3.svg.axis() + .scale(x) + .orient("bottom"); + + var yAxis = d3.svg.axis() + .scale(y) + .orient("left"); + + var area = d3.svg.area() + .x(function (d) { return x(d.date); }) + .y0(function (d) { return y(d.low); }) + .y1(function (d) { return y(d.high); }); + + var svg = d3.select("body").append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + d3.tsv("data.tsv", function (error, data) { + data.forEach(function (d) { + d.date = parseDate(d.date); + d.low = +d.low; + d.high = +d.high; + }); + + x.domain(d3.extent(data, function (d) { return d.date; })); + y.domain([d3.min(data, function (d) { return d.low; }), d3.max(data, function (d) { return d.high; })]); + + svg.append("path") + .datum(data) + .attr("class", "area") + .attr("d", area); + + svg.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") + .call(xAxis); + + svg.append("g") + .attr("class", "y axis") + .call(yAxis) + .append("text") + .attr("transform", "rotate(-90)") + .attr("y", 6) + .attr("dy", ".71em") + .style("text-anchor", "end") + .text("Temperature (şF)"); + }); +} diff --git a/d3/d3.d.ts b/d3/d3.d.ts index 10b084d8c..939ea0df4 100644 --- a/d3/d3.d.ts +++ b/d3/d3.d.ts @@ -1,387 +1,727 @@ -interface ID3Selectors { - select: (selector: string) => ID3Selection; - selectAll: (selector: string) => ID3Selection; -} - -interface ID3behavior -{ - drag: () => any; - zoom: () => any; -} - -interface ID3event -{ - dx: number; - dy: number; - clientX: number; - clientY: number; - translate:number[]; - scale: number; - sourceEvent: ID3event; -} - -interface ID3Base extends ID3Selectors { - // behavior - behavior: ID3behavior; - event: ID3event; - // Array Helpers - ascending: (a: number, b: number) => number; - descending: (a: number, b: number) => number; - min: (arr: any[], map?: (v: any) => any ) => any; - max: (arr: any[], map?: (v: any) => any ) => any; - extent: (arr: any[], map?: (v: any) => any ) => any[]; - quantile: (arr: number[], p: number) => number; - bisectLeft: (arr: any[], x: any, low?: number, high?: number) => number; - bisect: (arr: any[], x: any, low?: number, high?: number) => number; - bisectRight: (arr: any[], x: any, low?: number, high?: number) => number; - first: (arr: any[], comparator: (a: any, b: any) => any ) => any; - last: (arr: any[], comparator: (a: any, b:any) => any ) => any; - - // Loading resources - xhr: { - (url: string, callback: (xhr: XMLHttpRequest) => void): void; - (url: string, mime: string, callback: (xhr: XMLHttpRequest) => void): void; - }; - text: { - (url: string, callback: (response: string) => void): void; - (url: string, mime: string, callback: (response: string) => void): void; - }; - json: (url: string, callback: (response: any) => void) => void; - xml: { - (url: string, callback: (response: Document) => void): void; - (url: string, mime: string, callback: (response: Document) => void): void; - }; - html: (url: string, callback: (response: DocumentFragment) => void) => void; - csv: { - (url: string, callback: (error: any, response: any[]) => void); - parse(string: string): any[]; - parseRows(string: string, accessor: (row: any[], index: number) => any): any; - format(rows: any[]): string; - }; - - time: ID3Time; - scale: { - linear(): ID3LinearScale; - ordinal(): ID3OrdinalScale; - category10(): ID3OrdinalScale; - category20(): ID3OrdinalScale; - category20b(): ID3OrdinalScale; - category20c(): ID3OrdinalScale; - }; - interpolate: ID3BaseInterpolate; - interpolateNumber: ID3BaseInterpolate; - interpolateRound: ID3BaseInterpolate; - interpolateString: ID3BaseInterpolate; - interpolateRgb: ID3BaseInterpolate; - interpolateHsl: ID3BaseInterpolate; - interpolateArray: ID3BaseInterpolate; - interpolateObject: ID3BaseInterpolate; - interpolateTransform: ID3BaseInterpolate; - layout: ID3Layout; - svg: ID3Svg; - random: ID3Random; - keys(map: Object): any[]; -} - -interface ID3Selection extends ID3Selectors { - attr: { - (name: string): string; - (name: string, value: any): ID3Selection; - (name: string, valueFunction: (data: any, index: number) => any): ID3Selection; - }; - - classed: { - (name: string): string; - (name: string, value: any): ID3Selection; - (name: string, valueFunction: (data: any, index: number) => any): ID3Selection; - }; - - style: { - (name: string): string; - (name: string, value: any, priority?: string): ID3Selection; - (name: string, valueFunction: (data: any, index: number) => any, priority?: string): ID3Selection; - }; - - property: { - (name: string): void; - (name: string, value: any): ID3Selection; - (name: string, valueFunction: (data: any, index: number) => any): ID3Selection; - }; - - text: { - (): string; - (value: any): ID3Selection; - (valueFunction: (data: any, index: number) => any): ID3Selection; - }; - - html: { - (): string; - (value: any): ID3Selection; - (valueFunction: (data: any, index: number) => any): ID3Selection; - }; - - append: (name: string) => ID3Selection; - insert: (name: string, before: string) => ID3Selection; - remove: () => ID3Selection; - - data: { - (values: (data: any, index: number) => any) : any; - (values: any[], key?: (data: any, index: number) => any): ID3UpdateSelection; - }; - - call(callback: (selection: ID3Selection) => void): ID3Selection; -} - -interface ID3EnterSelection { - append: (name: string) => ID3Selection; - insert: (name: string, before: string) => ID3Selection; - select: (selector: string) => ID3Selection; - empty: () => bool; - node: () => Node; -} - -interface ID3UpdateSelection extends ID3Selection { - enter: () => ID3EnterSelection; - update: () => ID3Selection; - exit: () => ID3Selection; -} - -interface ID3Time { - second: ID3Interval; - minute: ID3Interval; - hour: ID3Interval; - day: ID3Interval; - week: ID3Interval; - sunday: ID3Interval; - monday: ID3Interval; - tuesday: ID3Interval; - wednesday: ID3Interval; - thursday: ID3Interval; - friday: ID3Interval; - saturday: ID3Interval; - month: ID3Interval; - year: ID3Interval; - - seconds: ID3Range; - minutes: ID3Range; - hours: ID3Range; - days: ID3Range; - weeks: ID3Range; - months: ID3Range; - years: ID3Range; - - sundays: ID3Range; - mondays: ID3Range; - tuesdays: ID3Range; - wednesdays: ID3Range; - thursdays: ID3Range; - fridays: ID3Range; - saturdays: ID3Range; - format: { - - (specifier: string): ID3TimeFormat; - utc: (specifier: string) => ID3TimeFormat; - iso: ID3TimeFormat; - }; - - scale(): ID3TimeScale; -} - -interface ID3Range { - (start: Date, end: Date, step?: number): Date[]; -} - -interface ID3Interval { - (date: Date): Date; - floor: (date: Date) => Date; - round: (date: Date) => Date; - ceil: (date: Date) => Date; - range: ID3Range; - offset: (date: Date, step: number) => Date; - utc: ID3Interval; -} - -interface ID3TimeFormat { - (date: Date): string; - parse: (string: string) => Date; -} - -interface ID3LinearScale { - (value: number): number; - invert(value: number): number; - domain(numbers: any[]): ID3LinearScale; - range: { - (values: any[]): ID3LinearScale; - (): any[]; - }; - rangeRound: (values: any[]) => ID3LinearScale; - interpolate: { - (): ID3Interpolate; - (factory: ID3Interpolate): ID3LinearScale; - }; - clamp(clamp: bool): ID3LinearScale; - nice(): ID3LinearScale; - ticks(count: number): any[]; - tickFormat(count: number): (n: number) => string; - copy: ID3LinearScale; -} - -interface ID3OrdinalScale { - (value: any): any; - domain(values: any[]): ID3OrdinalScale; - range: { - (values: any[]): ID3OrdinalScale; - (): any[]; - }; - rangePoints(interval: any[], padding?: number): ID3OrdinalScale; - rangeBands(interval: any[], padding?: number, outerPadding?: number): ID3OrdinalScale; - rangeRoundBands(interval: any[], padding?: number, outerPadding?: number): ID3OrdinalScale; - rangeBand(): any[]; - rangeExtent(): any[]; - copy: ID3OrdinalScale; -} - -interface ID3TimeScale { - (value: Date): number; - invert(value: number): Date; - domain(numbers: any[]): ID3TimeScale; - range: { - (values: any[]): ID3TimeScale; - (): any[]; - }; - rangeRound: (values: any[]) => ID3TimeScale; - interpolate: { - (): ID3Interpolate; - (factory: ID3InterpolateFactory): ID3TimeScale; - }; - clamp(clamp: bool): ID3TimeScale; - ticks: { - (count: number): any[]; - (range: ID3Range, count: number): any[]; - }; - tickFormat(count: number): (n: number) => string; - copy(): ID3TimeScale; -} - -interface ID3InterpolateFactory { - (a: any, b: any): ID3BaseInterpolate; -} -interface ID3BaseInterpolate { - (a: any, b: any): ID3Interpolate; -} - -interface ID3Interpolate { - (t: number): number; -} - -interface ID3Layout { - stack(): ID3StackLayout; - pie(): ID3PieLayout; -} - -interface ID3StackLayout { - (layers: any[], index?: number): any[]; - values(accessor?: (d: any) => any): ID3StackLayout; - offset(offset: string): ID3StackLayout; -} - -interface ID3PieLayout { - (values: any[], index?: number): ID3ArcDescriptor[]; - value: { - (): (d: any, index: number) => number; - (accessor: (d: any, index: number) => number): ID3PieLayout; - }; - sort: { - (): (d1: any, d2: any) => number; - (comparator: (d1: any, d2: any) => number): ID3PieLayout; - }; - startAngle: { - (): number; - (angle: number): ID3SvgArc; - (angle: () => number): ID3SvgArc; - }; - endAngle: { - (): number; - (angle: number): ID3SvgArc; - (angle: () => number): ID3SvgArc; - }; -} - -interface ID3ArcDescriptor { - value: any; - data: any; - startAngle: number; - endAngle: number; -} - -interface ID3SVGSymbol -{ - type: (string) => ID3SVGSymbol; - size: (number) => ID3SVGSymbol; -} - -interface ID3Svg { - symbol: ()=> ID3SVGSymbol; - axis(): ID3SvgAxis; - arc(): ID3SvgArc; -} - -interface ID3SvgAxis { - (selection: ID3Selection): void; - scale: { - (): any; - (scale: any): ID3SvgAxis; - }; - - orient: { - (): string; - (orientation: string): ID3SvgAxis; - }; - - ticks: { - (count: number): ID3SvgAxis; - (range: ID3Range, count?: number): ID3SvgAxis; - }; - - tickSubdivide(count: number): ID3SvgAxis; - tickSize(major?: number, minor?: number, end?: number): ID3SvgAxis; - tickFormat(formatter: (value: any) => string): ID3SvgAxis; -} - -interface ID3SvgArc { - (options?: ID3SvgArcOptions): string; - innerRadius: { - (): number; - (radius: number): ID3SvgArc; - (radius: () => number): ID3SvgArc; - }; - outerRadius: { - (): number; - (radius: number): ID3SvgArc; - (radius: () => number): ID3SvgArc; - }; - startAngle: { - (): number; - (angle: number): ID3SvgArc; - (angle: () => number): ID3SvgArc; - }; - endAngle: { - (): number; - (angle: number): ID3SvgArc; - (angle: () => number): ID3SvgArc; - }; - centroid(options?: ID3SvgArcOptions): number[]; -} - -interface ID3SvgArcOptions { - innerRadius?: number; - outerRadius?: number; - startAngle?: number; - endAngle?: number; -} - -interface ID3Random { - normal(mean?: number, deviation?: number): () => number; -} - -declare var d3: ID3Base; +interface ID3Selectors { + select: { + (selector: string): ID3Selection; + (element: Element): ID3Selection; + }; + selectAll: { + (selector: string): ID3Selection; + (elements: Element[]): ID3Selection; + }; +} + +interface ID3behavior +{ + drag: () => any; + zoom: () => any; +} + +interface ID3event +{ + dx: number; + dy: number; + clientX: number; + clientY: number; + translate:number[]; + scale: number; + sourceEvent: ID3event; +} + +interface ID3Base extends ID3Selectors { + // behavior + behavior: ID3behavior; + event: ID3event; + // Array Helpers + ascending: (a: number, b: number) => number; + descending: (a: number, b: number) => number; + min: (arr: any[], map?: (v: any) => any ) => any; + max: (arr: any[], map?: (v: any) => any ) => any; + extent: (arr: any[], map?: (v: any) => any ) => any[]; + quantile: (arr: number[], p: number) => number; + bisectLeft: (arr: any[], x: any, low?: number, high?: number) => number; + bisect: (arr: any[], x: any, low?: number, high?: number) => number; + bisectRight: (arr: any[], x: any, low?: number, high?: number) => number; + first: (arr: any[], comparator: (a: any, b: any) => any ) => any; + last: (arr: any[], comparator: (a: any, b: any) => any) => any; + range:(start: number, stop?: number, step?: number) => number[]; + + // Loading resources + xhr: { + (url: string, callback: (xhr: XMLHttpRequest) => void): void; + (url: string, mime: string, callback: (xhr: XMLHttpRequest) => void): void; + }; + text: { + (url: string, callback: (response: string) => void): void; + (url: string, mime: string, callback: (response: string) => void): void; + }; + json: (url: string, callback: (response: any) => void) => void; + xml: { + (url: string, callback: (response: Document) => void): void; + (url: string, mime: string, callback: (response: Document) => void): void; + }; + html: (url: string, callback: (response: DocumentFragment) => void) => void; + csv: { + (url: string, callback: (error: any, response: any[]) => void); + parse(string: string): any[]; + parseRows(string: string, accessor: (row: any[], index: number) => any): any; + format(rows: any[]): string; + }; + tsv: { + (url: string, callback: (error: any, response: any[]) => void ); + parse(string: string): any[]; + parseRows(string: string, accessor: (row: any[], index: number) => any): any; + format(rows: any[]): string; + }; + + time: ID3Time; + scale: { + linear(): ID3LinearScale; + ordinal(): ID3OrdinalScale; + quantize(): ID3QuantizeScale; + category10(): ID3OrdinalScale; + category20(): ID3OrdinalScale; + category20b(): ID3OrdinalScale; + category20c(): ID3OrdinalScale; + }; + interpolate: ID3BaseInterpolate; + interpolateNumber: ID3BaseInterpolate; + interpolateRound: ID3BaseInterpolate; + interpolateString: ID3BaseInterpolate; + interpolateRgb: ID3BaseInterpolate; + interpolateHsl: ID3BaseInterpolate; + interpolateArray: ID3BaseInterpolate; + interpolateObject: ID3BaseInterpolate; + interpolateTransform: ID3BaseInterpolate; + layout: ID3Layout; + svg: ID3Svg; + random: ID3Random; + keys(map: Object): any[]; + + format(specifier: string): (value: number) => string; + + nest(): ID3Nest; +} + +interface ID3Selection extends ID3Selectors { + attr: { + (name: string): string; + (name: string, value: any): ID3Selection; + (name: string, valueFunction: (data: any, index: number) => any): ID3Selection; + }; + + classed: { + (name: string): string; + (name: string, value: any): ID3Selection; + (name: string, valueFunction: (data: any, index: number) => any): ID3Selection; + }; + + style: { + (name: string): string; + (name: string, value: any, priority?: string): ID3Selection; + (name: string, valueFunction: (data: any, index: number) => any, priority?: string): ID3Selection; + }; + + property: { + (name: string): void; + (name: string, value: any): ID3Selection; + (name: string, valueFunction: (data: any, index: number) => any): ID3Selection; + }; + + text: { + (): string; + (value: any): ID3Selection; + (valueFunction: (data: any, index: number) => any): ID3Selection; + }; + + html: { + (): string; + (value: any): ID3Selection; + (valueFunction: (data: any, index: number) => any): ID3Selection; + }; + + append: (name: string) => ID3Selection; + insert: (name: string, before: string) => ID3Selection; + remove: () => ID3Selection; + + data: { + (values: (data: any, index: number) => any): ID3UpdateSelection; + (values: any[], key?: (data: any, index: number) => any): ID3UpdateSelection; + }; + + datum: { + (values: (data: any, index: number) => any): ID3UpdateSelection; + (values: any): ID3UpdateSelection; + }; + + filter: { + (filter: (data: any, index: number) => bool): ID3UpdateSelection; + (filter: string): ID3UpdateSelection; + }; + + call(callback: (selection: ID3Selection) => void ): ID3Selection; + each(eachFunction: (data: any, index: number) => any): ID3Selection; + on: { + (type: string): (data: any, index: number) => any; + (type: string, listener: (data: any, index: number) => any, capture?: bool): ID3Selection; + }; + + transition: () => ID3Transition; +} + +interface ID3EnterSelection { + append: (name: string) => ID3Selection; + insert: (name: string, before: string) => ID3Selection; + select: (selector: string) => ID3Selection; + empty: () => bool; + node: () => Node; +} + +interface ID3UpdateSelection extends ID3Selection { + enter: () => ID3EnterSelection; + update: () => ID3Selection; + exit: () => ID3Selection; +} + +interface ID3Transition { + duration: { + (duration: number): ID3Transition; + (duration: (data: any, index: number) => any): ID3Transition; + }; + delay: { + (delay: number): ID3Transition; + (delay: (data: any, index: number) => any): ID3Transition; + }; + attr: { + (name: string): string; + (name: string, value: any): ID3Transition; + (name: string, valueFunction: (data: any, index: number) => any): ID3Transition; + }; + call(callback: (selection: ID3Selection) => void ): ID3Transition; + + select: (selector: string) => ID3Transition; + selectAll: (selector: string) => ID3Transition; +} + +interface ID3Nest { + key(keyFunction: (data: any, index: number) => any): ID3Nest; + rollup(rollupFunction: (data: any, index: number) => any): ID3Nest; + map(values: any[]): ID3Nest; +} + +interface ID3Time { + second: ID3Interval; + minute: ID3Interval; + hour: ID3Interval; + day: ID3Interval; + week: ID3Interval; + sunday: ID3Interval; + monday: ID3Interval; + tuesday: ID3Interval; + wednesday: ID3Interval; + thursday: ID3Interval; + friday: ID3Interval; + saturday: ID3Interval; + month: ID3Interval; + year: ID3Interval; + + seconds: ID3Range; + minutes: ID3Range; + hours: ID3Range; + days: ID3Range; + weeks: ID3Range; + months: ID3Range; + years: ID3Range; + + sundays: ID3Range; + mondays: ID3Range; + tuesdays: ID3Range; + wednesdays: ID3Range; + thursdays: ID3Range; + fridays: ID3Range; + saturdays: ID3Range; + format: { + + (specifier: string): ID3TimeFormat; + utc: (specifier: string) => ID3TimeFormat; + iso: ID3TimeFormat; + }; + + scale(): ID3TimeScale; +} + +interface ID3Range { + (start: Date, end: Date, step?: number): Date[]; +} + +interface ID3Interval { + (date: Date): Date; + floor: (date: Date) => Date; + round: (date: Date) => Date; + ceil: (date: Date) => Date; + range: ID3Range; + offset: (date: Date, step: number) => Date; + utc: ID3Interval; +} + +interface ID3TimeFormat { + (date: Date): string; + parse: (string: string) => Date; +} + +interface ID3LinearScale { + (value: number): number; + invert(value: number): number; + domain(numbers: any[]): ID3LinearScale; + range: { + (values: any[]): ID3LinearScale; + (): any[]; + }; + rangeRound: (values: any[]) => ID3LinearScale; + interpolate: { + (): ID3Interpolate; + (factory: ID3Interpolate): ID3LinearScale; + }; + clamp(clamp: bool): ID3LinearScale; + nice(): ID3LinearScale; + ticks(count: number): any[]; + tickFormat(count: number): (n: number) => string; + copy: ID3LinearScale; +} + +interface ID3OrdinalScale { + (value: any): any; + domain: { + (values: any[]): ID3OrdinalScale; + (): any[]; + }; + range: { + (values: any[]): ID3OrdinalScale; + (): any[]; + }; + rangePoints(interval: any[], padding?: number): ID3OrdinalScale; + rangeBands(interval: any[], padding?: number, outerPadding?: number): ID3OrdinalScale; + rangeRoundBands(interval: any[], padding?: number, outerPadding?: number): ID3OrdinalScale; + rangeBand(): number; + rangeExtent(): any[]; + copy: ID3OrdinalScale; +} + +interface ID3QuantizeScale { + (value: any): any; + domain: { + (values: number[]): ID3QuantizeScale; + (): any[]; + }; + range: { + (values: any[]): ID3QuantizeScale; + (): any[]; + }; + copy: ID3QuantizeScale; +} + +interface ID3TimeScale { + (value: Date): number; + invert(value: number): Date; + domain(numbers: any[]): ID3TimeScale; + range: { + (values: any[]): ID3TimeScale; + (): any[]; + }; + rangeRound: (values: any[]) => ID3TimeScale; + interpolate: { + (): ID3Interpolate; + (factory: ID3InterpolateFactory): ID3TimeScale; + }; + clamp(clamp: bool): ID3TimeScale; + ticks: { + (count: number): any[]; + (range: ID3Range, count: number): any[]; + }; + tickFormat(count: number): (n: number) => string; + copy(): ID3TimeScale; +} + +interface ID3InterpolateFactory { + (a: any, b: any): ID3BaseInterpolate; +} +interface ID3BaseInterpolate { + (a: any, b: any): ID3Interpolate; +} + +interface ID3Interpolate { + (t: number): number; +} + +interface ID3Layout { + stack(): ID3StackLayout; + pie(): ID3PieLayout; +} + +interface ID3StackLayout { + (layers: any[], index?: number): any[]; + values(accessor?: (d: any) => any): ID3StackLayout; + offset(offset: string): ID3StackLayout; +} + +interface ID3PieLayout { + (values: any[], index?: number): ID3ArcDescriptor[]; + value: { + (): (d: any, index: number) => number; + (accessor: (d: any, index: number) => number): ID3PieLayout; + }; + sort: { + (): (d1: any, d2: any) => number; + (comparator: (d1: any, d2: any) => number): ID3PieLayout; + }; + startAngle: { + (): number; + (angle: number): ID3SvgArc; + (angle: () => number): ID3SvgArc; + }; + endAngle: { + (): number; + (angle: number): ID3SvgArc; + (angle: () => number): ID3SvgArc; + }; +} + +interface ID3ArcDescriptor { + value: any; + data: any; + startAngle: number; + endAngle: number; +} + +interface ID3SVGSymbol +{ + type: (string) => ID3SVGSymbol; + size: (number) => ID3SVGSymbol; +} + +interface ID3Svg { + /** + * Create a new symbol generator + */ + symbol: () => ID3SVGSymbol; + /** + * Create a new axis generator + */ + axis(): ID3SvgAxis; + /** + * Create a new arc generator + */ + arc(): ID3SvgArc; + /** + * Create a new line generator + */ + line(): ID3SvgLine; + /** + * Create a new area generator + */ + area(): ID3SvgArea; +} + +interface ID3SvgAxis { + (selection: ID3Selection): void; + scale: { + (): any; + (scale: any): ID3SvgAxis; + }; + + orient: { + (): string; + (orientation: string): ID3SvgAxis; + }; + + ticks: { + (count: number): ID3SvgAxis; + (range: ID3Range, count?: number): ID3SvgAxis; + }; + + tickSubdivide(count: number): ID3SvgAxis; + tickSize(major?: number, minor?: number, end?: number): ID3SvgAxis; + tickFormat(formatter: (value: any) => string): ID3SvgAxis; +} + +interface ID3SvgArc { + (options?: ID3SvgArcOptions): string; + innerRadius: { + (): number; + (radius: number): ID3SvgArc; + (radius: () => number): ID3SvgArc; + }; + outerRadius: { + (): number; + (radius: number): ID3SvgArc; + (radius: () => number): ID3SvgArc; + }; + startAngle: { + (): number; + (angle: number): ID3SvgArc; + (angle: () => number): ID3SvgArc; + }; + endAngle: { + (): number; + (angle: number): ID3SvgArc; + (angle: () => number): ID3SvgArc; + }; + centroid(options?: ID3SvgArcOptions): number[]; +} + +interface ID3SvgArcOptions { + innerRadius?: number; + outerRadius?: number; + startAngle?: number; + endAngle?: number; +} + +interface ID3SvgLine { + /** + * Returns the path data string + * + * @param data Array of data elements + * @param index Optional index + */ + (data: any[], index?: number): string; + /** + * Get or set the x-coordinate accessor. + */ + x: { + /** + * Get the x-coordinate accessor. + */ + (): (data: any) => any; + /** + * Set the x-coordinate accessor. + * + * @param accessor The new accessor function + */ + (accessor: (data: any) => any): ID3SvgLine; + }; + /** + * Get or set the y-coordinate accessor. + */ + y: { + /** + * Get the y-coordinate accessor. + */ + (): (data: any) => any; + /** + * Set the y-coordinate accessor. + * + * @param accessor The new accessor function + */ + (accessor: (data: any) => any): ID3SvgLine; + }; + /** + * Get or set the interpolation mode. + */ + interpolate: { + /** + * Get the interpolation accessor. + */ + (): string; + /** + * Set the interpolation accessor. + * + * @param interpolate The interpolation mode + */ + (interpolate: string): ID3SvgLine; + }; + /** + * Get or set the cardinal spline tension. + */ + tension: { + /** + * Get the cardinal spline accessor. + */ + (): number; + /** + * Set the cardinal spline accessor. + * + * @param tension The Cardinal spline interpolation tension + */ + (tension: number): ID3SvgLine; + }; + /** + * Control whether the line is defined at a given point. + */ + defined: { + /** + * Get the accessor function that controls where the line is defined. + */ + (): (data: any) => any; + /** + * Set the accessor function that controls where the area is defined. + * + * @param defined The new accessor function + */ + (defined: (data: any) => any): ID3SvgLine; + }; +} + +interface ID3SvgArea { + /** + * Generate a piecewise linear area, as in an area chart. + */ + (data: any[], index?: number): string; + /** + * Get or set the x-coordinate accessor. + */ + x: { + /** + * Get the x-coordinate accessor. + */ + (): (data: any) => any; + /** + * Set the x-coordinate accessor. + * + * @param accessor The new accessor function + */ + (accessor: (data: any) => any): ID3SvgArea; + }; + /** + * Get or set the x0-coordinate (baseline) accessor. + */ + x0: { + /** + * Get the x0-coordinate (baseline) accessor. + */ + (): (data: any) => any; + /** + * Set the x0-coordinate (baseline) accessor. + * + * @param accessor The new accessor function + */ + (accessor: (data: any) => any): ID3SvgArea; + }; + /** + * Get or set the x1-coordinate (topline) accessor. + */ + x1: { + /** + * Get the x1-coordinate (topline) accessor. + */ + (): (data: any) => any; + /** + * Set the x1-coordinate (topline) accessor. + * + * @param accessor The new accessor function + */ + (accessor: (data: any) => any): ID3SvgArea; + }; + /** + * Get or set the y-coordinate accessor. + */ + y: { + /** + * Get the y-coordinate accessor. + */ + (): (data: any) => any; + /** + * Set the y-coordinate accessor. + * + * @param accessor The new accessor function + */ + (accessor: (data: any) => any): ID3SvgArea; + }; + /** + * Get or set the y0-coordinate (baseline) accessor. + */ + y0: { + /** + * Get the y0-coordinate (baseline) accessor. + */ + (): (data: any) => any; + /** + * Set the y0-coordinate (baseline) accessor. + * + * @param accessor The new accessor function + */ + (accessor: (data: any) => any): ID3SvgArea; + }; + /** + * Get or set the y1-coordinate (topline) accessor. + */ + y1: { + /** + * Get the y1-coordinate (topline) accessor. + */ + (): (data: any) => any; + /** + * Set the y1-coordinate (topline) accessor. + * + * @param accessor The new accessor function + */ + (accessor: (data: any) => any): ID3SvgArea; + }; + /** + * Get or set the interpolation mode. + */ + interpolate: { + /** + * Get the interpolation accessor. + */ + (): string; + /** + * Set the interpolation accessor. + * + * @param interpolate The interpolation mode + */ + (interpolate: string): ID3SvgArea; + }; + /** + * Get or set the cardinal spline tension. + */ + tension: { + /** + * Get the cardinal spline accessor. + */ + (): number; + /** + * Set the cardinal spline accessor. + * + * @param tension The Cardinal spline interpolation tension + */ + (tension: number): ID3SvgArea; + }; + /** + * Control whether the area is defined at a given point. + */ + defined: { + /** + * Get the accessor function that controls where the area is defined. + */ + (): (data: any) => any; + /** + * Set the accessor function that controls where the area is defined. + * + * @param defined The new accessor function + */ + (defined: (data: any) => any): ID3SvgArea; + }; +} + +interface ID3Random { + /** + * Returns a function for generating random numbers with a normal distribution + * + * @param mean The expected value of the generated pseudorandom numbers + * @param deviation The given standard deviation + */ + normal(mean?: number, deviation?: number): () => number; + /** + * Returns a function for generating random numbers with a log-normal distribution + * + * @param mean The expected value of the generated pseudorandom numbers + * @param deviation The given standard deviation + */ + logNormal(mean?: number, deviation?: number): () => number; + /** + * Returns a function for generating random numbers with an Irwin-Hall distribution + * + * @param count The number of independent variables + */ + irwinHall(count: number): () => number; +} + +declare var d3: ID3Base; diff --git a/express/express-tests.ts b/express/express-tests.ts index 9b54b1701..53dfc1143 100644 --- a/express/express-tests.ts +++ b/express/express-tests.ts @@ -1,10 +1,1250 @@ /// -declare var _, $; +import express = module('express'); +var app = express(); -import Express = module('express'); -var express: Express; -var app: Express.ServerApplication; +////////////////////////// + +var hash: any; + +// config + +app.set('view engine', 'ejs'); +app.set('views', __dirname + '/views'); + +// middleware + +app.use(express.bodyParser()); +app.use(express.cookieParser('shhhh, very secret')); +app.use(express.session()); + +// Session-persisted message middleware + +app.use(function (req, res, next) { + var err = req.session.error + , msg = req.session.success; + delete req.session.error; + delete req.session.success; + res.locals.message = ''; + if (err) res.locals.message = '

' + err + '

'; + if (msg) res.locals.message = '

' + msg + '

'; + next(); +}); + +// dummy database + +var users = { + tj: { name: 'tj' } +}; + +// when you create a user, generate a salt +// and hash the password ('foobar' is the pass here) + +hash('foobar', function (err, salt, hash) { + if (err) throw err; + // store the salt & hash in the "db" + users.tj.salt = salt; + users.tj.hash = hash; +}); + + +// Authenticate using our plain-object database of doom! + +function authenticate(name, pass, fn) { + if (!module.parent) console.log('authenticating %s:%s', name, pass); + var user = users[name]; + // query the db for the given username + if (!user) return fn(new Error('cannot find user')); + // apply the same algorithm to the POSTed password, applying + // the hash against the pass / salt, if there is a match we + // found the user + hash(pass, user.salt, function (err, hash) { + if (err) return fn(err); + if (hash == user.hash) return fn(null, user); + fn(new Error('invalid password')); + }) +} + +function restrict(req: ExpressServerRequest, res: ExpressServerResponse, next?: Function) { + if (req.session.user) { + next(); + } else { + req.session.error = 'Access denied!'; + res.redirect('/login'); + } +} + +app.get('/', function (req, res) { + res.redirect('login'); +}); + +app.get('/restricted', restrict, function (req, res) { + res.send('Wahoo! restricted area, click to logout'); +}); + +app.get('/logout', function (req, res) { + // destroy the user's session to log them out + // will be re-created next request + req.session.destroy(function () { + res.redirect('/'); + }); +}); + +app.get('/login', function (req, res) { + res.render('login'); +}); + +app.post('/login', function (req, res) { + authenticate(req.body.username, req.body.password, function (err, user) { + if (user) { + // Regenerate session when signing in + // to prevent fixation + req.session.regenerate(function () { + // Store the user's primary key + // in the session store to be retrieved, + // or in this case the entire user object + req.session.user = user; + req.session.success = 'Authenticated as ' + user.name + + ' click to logout. ' + + ' You may now access /restricted.'; + res.redirect('back'); + }); + } else { + req.session.error = 'Authentication failed, please check your ' + + ' username and password.' + + ' (use "tj" and "foobar")'; + res.redirect('login'); + } + }); +}); + +if (!module.parent) { + app.listen(3000); + console.log('Express started on port 3000'); +} + +////////////// + +app.set('views', __dirname); +app.set('view engine', 'jade'); + +var pets = []; + +var n = 1000; +while (n--) { + pets.push({ name: 'Tobi', age: 2, species: 'ferret' }); + pets.push({ name: 'Loki', age: 1, species: 'ferret' }); + pets.push({ name: 'Jane', age: 6, species: 'ferret' }); +} + +app.use(express.logger('dev')); + +app.get('/', function (req, res) { + res.render('pets', { pets: pets }); +}); + +app.listen(3000); +console.log('Express listening on port 3000'); + +///////////// + +app.get('/', function (req, res) { + res.format({ + html: function () { + res.send('
    ' + users.map(function (user) { + return '
  • ' + user.name + '
  • '; + }).join('') + '
'); + }, + + text: function () { + res.send(users.map(function (user) { + return ' - ' + user.name + '\n'; + }).join('')); + }, + + json: function () { + res.json(users); + } + }) +}); + +// or you could write a tiny middleware like +// this to abstract make things a bit more declarative: + +function format(mod) { + var obj = require(mod); + return function (req, res) { + res.format(obj); + } +} + +app.get('/users', format('./users')); + +if (!module.parent) { + app.listen(3000); + console.log('listening on port 3000'); +} + +///////////////////////// + +// add favicon() before logger() so +// GET /favicon.ico requests are not +// logged, because this middleware +// reponds to /favicon.ico and does not +// call next() +app.use(express.favicon()); + +// custom log format +if ('test' != process.env.NODE_ENV) + app.use(express.logger(':method :url')); + +// parses request cookies, populating +// req.cookies and req.signedCookies +// when the secret is passed, used +// for signing the cookies. +app.use(express.cookieParser('my secret here')); + +// parses json, x-www-form-urlencoded, and multipart/form-data +app.use(express.bodyParser()); + +app.get('/', function (req, res) { + if (req.cookies.remember) { + res.send('Remembered :). Click to forget!.'); + } else { + res.send('

Check to ' + + '.

'); + } +}); + +app.get('/forget', function (req, res) { + res.clearCookie('remember'); + res.redirect('back'); +}); + +app.post('/', function (req, res) { + var minute = 60000; + if (req.body.remember) res.cookie('remember', 1, { maxAge: minute }); + res.redirect('back'); +}); + +if (!module.parent) { + app.listen(3000); + console.log('Express started on port 3000'); +} + +/////////////////// + +// ignore GET /favicon.ico +app.use(express.favicon()); + +// pass a secret to cookieParser() for signed cookies +app.use(express.cookieParser('manny is cool')); + +// add req.session cookie support +app.use(express.cookieSession()); + +// do something with the session +app.use(count); + +// custom middleware +function count(req, res) { + req.session.count = req.session.count || 0; + var n = req.session.count++; + res.send('viewed ' + n + ' times\n'); +} + +if (!module.parent) { + app.listen(3000); + console.log('Express server listening on port 3000'); +} + +/////////////// + +var api = app; + +app.use(express.static(__dirname + '/public')); + +// api middleware + +api.use(express.logger('dev')); +api.use(express.bodyParser()); + +/** + * CORS support. + */ + +api.all('*', function (req, res, next) { + if (!req.get('Origin')) return next(); + // use "*" here to accept any origin + res.set('Access-Control-Allow-Origin', 'http://localhost:3000'); + res.set('Access-Control-Allow-Methods', 'GET, POST'); + res.set('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type'); + // res.set('Access-Control-Allow-Max-Age', 3600); + if ('OPTIONS' == req.method) return res.send(200); + next(); +}); + +/** + * POST a user. + */ + +api.post('/user', function (req, res) { + console.log(req.body); + res.send(201); +}); + +app.listen(3000); +api.listen(3001); + +console.log('app listening on 3000'); +console.log('api listening on 3001'); + +//////////////////// + +app.get('/', function (req, res) { + res.send(''); +}); + +// /files/* is accessed via req.params[0] +// but here we name it :file +app.get('/files/:file(*)', function (req, res, next?) { + var file = req.params.file + , path = __dirname + '/files/' + file; + + res.download(path); +}); + +// error handling middleware. Because it's +// below our routes, you will be able to +// "intercept" errors, otherwise Connect +// will respond with 500 "Internal Server Error". +app.use(function (err, req, res, next) { + // special-case 404s, + // remember you could + // render a 404 template here + if (404 == err.status) { + res.statusCode = 404; + res.send('Cant find that file, sorry!'); + } else { + next(err); + } +}); + +if (!module.parent) { + app.listen(3000); + console.log('Express started on port 3000'); +} + +/////////////////// + +// Register ejs as .html. If we did +// not call this, we would need to +// name our views foo.ejs instead +// of foo.html. The __express method +// is simply a function that engines +// use to hook into the Express view +// system by default, so if we want +// to change "foo.ejs" to "foo.html" +// we simply pass _any_ function, in this +// case `ejs.__express`. + +app.engine('.html', require('ejs').__express); + +// Optional since express defaults to CWD/views + +app.set('views', __dirname + '/views'); + +// Without this you would need to +// supply the extension to res.render() +// ex: res.render('users.html'). +app.set('view engine', 'html'); + +app.get('/', function (req, res) { + res.render('users', { + users: users, + title: "EJS example", + header: "Some users" + }); +}); + +if (!module.parent) { + app.listen(3000); + console.log('Express app started on port 3000'); +} + +//////////////////// + +var test: any; + +if (!test) app.use(express.logger('dev')); +app.use(app.router); + +// the error handler is strategically +// placed *below* the app.router; if it +// were above it would not receive errors +// from app.get() etc +app.use(error); + +// error handling middleware have an arity of 4 +// instead of the typical (req, res, next), +// otherwise they behave exactly like regular +// middleware, you may have several of them, +// in different orders etc. + +function error(err, req, res, next) { + // log it + if (!test) console.error(err.stack); + + // respond with 500 "Internal Server Error". + res.send(500); +} + +app.get('/', function (req, res) { + // Caught and passed down to the errorHandler middleware + throw new Error('something broke!'); +}); + +app.get('/next', function (req, res, next) { + // We can also pass exceptions to next() + process.nextTick(function () { + next(new Error('oh no!')); + }); +}); + +if (!module.parent) { + app.listen(3000); + console.log('Express started on port 3000'); +} + +///////////////////// + +var silent: any; + +// general config +app.set('views', __dirname + '/views'); +app.set('view engine', 'jade'); + +// our custom "verbose errors" setting +// which we can use in the templates +// via settings['verbose errors'] +app.enable('verbose errors'); + +// disable them in production +// use $ NODE_ENV=production node examples/error-pages +if ('production' == app.settings.env) { + app.disable('verbose errors'); +} + +app.use(express.favicon()); + +silent || app.use(express.logger('dev')); + +// "app.router" positions our routes +// above the middleware defined below, +// this means that Express will attempt +// to match & call routes _before_ continuing +// on, at which point we assume it's a 404 because +// no route has handled the request. + +app.use(app.router); + +// Since this is the last non-error-handling +// middleware use()d, we assume 404, as nothing else +// responded. + +// $ curl http://localhost:3000/notfound +// $ curl http://localhost:3000/notfound -H "Accept: application/json" +// $ curl http://localhost:3000/notfound -H "Accept: text/plain" + +app.use(function (req, res, next) { + res.status(404); + + // respond with html page + if (req.accepts('html')) { + res.render('404', { url: req.url }); + return; + } + + // respond with json + if (req.accepts('json')) { + res.send({ error: 'Not found' }); + return; + } + + // default to plain-text. send() + res.type('txt').send('Not found'); +}); + +// error-handling middleware, take the same form +// as regular middleware, however they require an +// arity of 4, aka the signature (err, req, res, next). +// when connect has an error, it will invoke ONLY error-handling +// middleware. + +// If we were to next() here any remaining non-error-handling +// middleware would then be executed, or if we next(err) to +// continue passing the error, only error-handling middleware +// would remain being executed, however here +// we simply respond with an error page. + +app.use(function (err, req, res, next) { + // we may use properties of the error object + // here and next(err) appropriately, or if + // we possibly recovered from the error, simply next(). + res.status(err.status || 500); + res.render('500', { error: err }); +}); + +// Routes + +app.get('/', function (req, res) { + res.render('index.jade'); +}); + +app.get('/404', function (req, res, next) { + // trigger a 404 since no other middleware + // will match /404 after this one, and we're not + // responding here + next(); +}); + +app.get('/403', function (req, res, next) { + // trigger a 403 error + var err = new Error('not allowed!'); + err.status = 403; + next(err); +}); + +app.get('/500', function (req, res, next) { + // trigger a generic (500) error + next(new Error('keyboard cat!')); +}); + +if (!module.parent) { + app.listen(3000); + //silent ||  console.log('Express started on port 3000'); +} + +/////////////// + +var fs: any; +var md: any; + +app.set('view engine', 'jade'); +app.set('views', __dirname + '/views'); + +function User(name) { + this.private = 'heyyyy'; + this.secret = 'something'; + this.name = name; + this.id = 123; +} + +// You'll probably want to do +// something like this so you +// dont expose "secret" data. + +User.prototype.toJSON = function () { + return { + id: this.id, + name: this.name + } +}; + +app.use(express.logger('dev')); + +// earlier on expose an object +// that we can tack properties on. +// all res.locals props are exposed +// to the templates, so "expose" will +// be present. + +app.use(function (req, res, next) { + res.locals.expose = {}; + // you could alias this as req or res.expose + // to make it shorter and less annoying + next(); +}); + +// pretend we loaded a user + +app.use(function (req, res, next) { + req.user = new User('Tobi'); + next(); +}); + +app.get('/', function (req, res) { + res.redirect('/user'); +}); + +app.get('/user', function (req, res) { + // we only want to expose the user + // to the client for this route: + res.locals.expose.user = req.user; + res.render('page'); +}); + +app.listen(3000); +console.log('app listening on port 3000'); + +/////////////////////// + +app.get('/', function (req, res) { + res.send('Hello World'); +}); + +app.listen(3000); +console.log('Express started on port 3000'); + +//////////////////// + +// register .md as an engine in express view system + +app.engine('md', function (path, options, fn) { + fs.readFile(path, 'utf8', function (err, str) { + if (err) return fn(err); + try { + var html = md(str); + html = html.replace(/\{([^}]+)\}/g, function (_, name) { + return options[name] || ''; + }) + fn(null, html); + } catch (err) { + fn(err); + } + }); +}) + +app.set('views', __dirname + '/views'); + +// make it the default so we dont need .md +app.set('view engine', 'md'); + +app.get('/', function (req, res) { + res.render('index', { title: 'Markdown Example' }); +}) + +app.get('/fail', function (req, res) { + res.render('missing', { title: 'Markdown Example' }); +}) + +if (!module.parent) { + app.listen(3000); + console.log('Express started on port 3000'); +} + +/////////////////////// + +var mformat: any; + +// bodyParser in connect 2.x uses node-formidable to parse +// the multipart form data. +app.use(express.bodyParser()) + +app.get('/', function (req, res) { + res.send('
' + + '

Title:

' + + '

Image:

' + + '

' + + '
'); +}); + +app.post('/', function (req, res, next) { + // the uploaded file can be found as `req.files.image` and the + // title field as `req.body.title` + res.send(mformat('\nuploaded %s (%d Kb) to %s as %s' + , req.files.image.name + , req.files.image.size / 1024 | 0 + , req.files.image.path + , req.body.title)); +}); + +if (!module.parent) { + app.listen(3000); + console.log('Express started on port 3000'); +} + +////////////////// + + +// first: +// $ npm install redis online +// $ redis-server + +/** + * Module dependencies. + */ + +var online: any; +var redis: any; +var db: any; + +// online + +online = online(db); + +// activity tracking, in this case using +// the UA string, you would use req.user.id etc + +app.use(function (req, res, next) { + // fire-and-forget + online.add(req.headers['user-agent']); + next(); +}); + +/** + * List helper. + */ + +function list(ids) { + return '
    ' + ids.map(function (id) { + return '
  • ' + id + '
  • '; + }).join('') + '
'; +} + +/** + * GET users online. + */ + +app.get('/', function (req, res, next) { + online.last(5, function (err, ids) { + if (err) return next(err); + res.send('

Users online: ' + ids.length + '

' + list(ids)); + }); +}); + +app.listen(3000); +console.log('listening on port 3000'); + +/////////////////// + +// Convert :to and :from to integers + +app.param(['to', 'from'], function (req, res, next, num, name) { + req.params[name] = num = parseInt(num, 10); + if (isNaN(num)) { + next(new Error('failed to parseInt ' + num)); + } else { + next(); + } +}); + +// Load user by id + +app.param('user', function (req, res, next, id) { + if (req.user = users[id]) { + next(); + } else { + next(new Error('failed to find user')); + } +}); + +/** + * GET index. + */ + +app.get('/', function (req, res) { + res.send('Visit /user/0 or /users/0-2'); +}); + +/** + * GET :user. + */ + +app.get('/user/:user', function (req, res, next) { + res.send('user ' + req.user.name); +}); + +/** + * GET users :from - :to. + */ + +app.get('/users/:from-:to', function (req, res, next) { + var from = req.params.from + , to = req.params.to + , names = users.map(function (user) { return user.name; }); + res.send('users ' + names.slice(from, to).join(', ')); +}); + +if (!module.parent) { + app.listen(3000); + console.log('Express started on port 3000'); +} + +////////////////// + +// Ad-hoc example resource method + +app.resource = function (path, obj) { + this.get(path, obj.index); + this.get(path + '/:a..:b.:format?', function (req, res) { + var a = parseInt(req.params.a, 10) + , b = parseInt(req.params.b, 10) + , format = req.params.format; + obj.range(req, res, a, b, format); + }); + this.get(path + '/:id', obj.show); + this.del(path + '/:id', obj.destroy); +}; + +// Fake controller. + +var FUser = { + index: function (req, res) { + res.send(users); + }, + show: function (req, res) { + res.send(users[req.params.id] || { error: 'Cannot find user' }); + }, + destroy: function (req, res) { + var id = req.params.id; + var destroyed = id in users; + delete users[id]; + res.send(destroyed ? 'destroyed' : 'Cannot find user'); + }, + range: function (req, res, a, b, format) { + var range = users.slice(a, b + 1); + switch (format) { + case 'json': + res.send(range); + break; + case 'html': + default: + var html = '
    ' + range.map(function (user) { + return '
  • ' + user.name + '
  • '; + }).join('\n') + '
'; + res.send(html); + break; + } + } +}; + +// curl http://localhost:3000/users -- responds with all users +// curl http://localhost:3000/users/1 -- responds with user 1 +// curl http://localhost:3000/users/4 -- responds with error +// curl http://localhost:3000/users/1..3 -- responds with several users +// curl -X DELETE http://localhost:3000/users/1 -- deletes the user + +app.resource('/users', FUser); + +app.get('/', function (req, res) { + res.send([ + '

Examples:

    ' + , '
  • GET /users
  • ' + , '
  • GET /users/1
  • ' + , '
  • GET /users/3
  • ' + , '
  • GET /users/1..3
  • ' + , '
  • GET /users/1..3.json
  • ' + , '
  • DELETE /users/4
  • ' + , '
' + ].join('\n')); +}); + +if (!module.parent) { + app.listen(3000); + console.log('Express started on port 3000'); +} + +///////////////////// + + +var verbose: any; + +app.map = function (a, route) { + route = route || ''; + for (var key in a) { + switch (typeof a[key]) { + // { '/path': { ... }} + case 'object': + app.map(a[key], route + key); + break; + // get: function(){ ... } + case 'function': + if (verbose) console.log('%s %s', key, route); + app[key](route, a[key]); + break; + } + } +}; + +var users2 = { + list: function (req, res) { + res.send('user list'); + }, + + get: function (req, res) { + res.send('user ' + req.params.uid); + }, + + del: function (req, res) { + res.send('delete users'); + } +}; + +var pets2 = { + list: function (req, res) { + res.send('user ' + req.params.uid + '\'s pets'); + }, + + del: function (req, res) { + res.send('delete ' + req.params.uid + '\'s pet ' + req.params.pid); + } +}; + +app.map({ + '/users': { + get: users2.list, + del: users2.del, + '/:uid': { + get: users.get , + '/pets': { + get: pets2.list, + '/:pid': { + del: pets2.del + } + } + } + } +}); + +app.listen(3000); + +/////////////////////////// + +// Example requests: +// curl http://localhost:3000/user/0 +// curl http://localhost:3000/user/0/edit +// curl http://localhost:3000/user/1 +// curl http://localhost:3000/user/1/edit (unauthorized since this is not you) +// curl -X DELETE http://localhost:3000/user/0 (unauthorized since you are not an admin) + +function loadUser(req, res, next) { + // You would fetch your user from the db + var user = users[req.params.id]; + if (user) { + req.user = user; + next(); + } else { + next(new Error('Failed to load user ' + req.params.id)); + } +} + +function andRestrictToSelf(req, res, next) { + // If our authenticated user is the user we are viewing + // then everything is fine :) + if (req.authenticatedUser.id == req.user.id) { + next(); + } else { + // You may want to implement specific exceptions + // such as UnauthorizedError or similar so that you + // can handle these can be special-cased in an error handler + // (view ./examples/pages for this) + next(new Error('Unauthorized')); + } +} + +function andRestrictTo(role) { + return function (req, res, next) { + if (req.authenticatedUser.role == role) { + next(); + } else { + next(new Error('Unauthorized')); + } + } +} + +// Middleware for faux authentication +// you would of course implement something real, +// but this illustrates how an authenticated user +// may interact with middleware + +app.use(function (req, res, next) { + req.authenticatedUser = users[0]; + next(); +}); + +app.get('/', function (req, res) { + res.redirect('/user/0'); +}); + +app.get('/user/:id', loadUser, function (req, res) { + res.send('Viewing user ' + req.user.name); +}); + +app.get('/user/:id/edit', loadUser, andRestrictToSelf, function (req, res) { + res.send('Editing user ' + req.user.name); +}); + +app.del('/user/:id', loadUser, andRestrictTo('admin'), function (req, res) { + res.send('Deleted user ' + req.user.name); +}); + +app.listen(3000); +console.log('Express app started on port 3000'); + +///////////////////////// + +app.set('view engine', 'jade'); +app.set('views', __dirname); + +// populate search + +db.sadd('ferret', 'tobi'); +db.sadd('ferret', 'loki'); +db.sadd('ferret', 'jane'); +db.sadd('cat', 'manny'); +db.sadd('cat', 'luna'); + +/** + * GET the search page. + */ + +app.get('/', function (req, res) { + res.render('search'); +}); + +/** + * GET search for :query. + */ + +app.get('/search/:query?', function (req, res) { + var query = req.params.query; + db.smembers(query, function (err, vals) { + if (err) return res.send(500); + res.send(vals); + }); +}); + +/** + * GET client javascript. Here we use sendfile() + * because serving __dirname with the static() middleware + * would also mean serving our server "index.js" and the "search.jade" + * template. + */ + +app.get('/client.js', function (req, res) { + res.sendfile(__dirname + '/client.js'); +}); + +app.listen(3000); +console.log('app listening on port 3000'); + +/////////////////// + +app.use(express.logger('dev')); + +// Required by session() middleware +// pass the secret for signed cookies +// (required by session()) +app.use(express.cookieParser('keyboard cat')); + +// Populates req.session +app.use(express.session()); + +app.get('/', function (req, res) { + var body = ''; + if (req.session.views) { + ++req.session.views; + } else { + req.session.views = 1; + body += '

First time visiting? view this page in several browsers :)

'; + } + res.send(body + '

viewed ' + req.session.views + ' times.

'); +}); + +app.listen(3000); +console.log('Express app started on port 3000'); + +//////////////////////// + +// log requests +app.use(express.logger('dev')); + +// express on its own has no notion +// of a "file". The express.static() +// middleware checks for a file matching +// the `req.path` within the directory +// that you pass it. In this case "GET /js/app.js" +// will look for "./public/js/app.js". + +app.use(express.static(__dirname + '/public')); + +// if you wanted to "prefix" you may use +// the mounting feature of Connect, for example +// "GET /static/js/app.js" instead of "GET /js/app.js". +// The mount-path "/static" is simply removed before +// passing control to the express.static() middleware, +// thus it serves the file correctly by ignoring "/static" +app.use('/static', express.static(__dirname + '/public')); + +// if for some reason you want to serve files from +// several directories, you can use express.static() +// multiple times! Here we're passing "./public/css", +// this will allow "GET /style.css" instead of "GET /css/style.css": +app.use(express.static(__dirname + '/public/css')); + +// this examples does not have any routes, however +// you may `app.use(app.router)` before or after these +// static() middleware. If placed before them your routes +// will be matched BEFORE file serving takes place. If placed +// after as shown here then file serving is performed BEFORE +// any routes are hit: +app.use(app.router); + +app.listen(3000); +console.log('listening on port 3000'); +console.log('try:'); +console.log(' GET /hello.txt'); +console.log(' GET /js/app.js'); +console.log(' GET /css/style.css'); + +////////////////// + +/* +edit /etc/vhosts: + +127.0.0.1 foo.example.com +127.0.0.1 bar.example.com +127.0.0.1 example.com +*/ + +// Main app + +var main = express(); + +main.use(express.logger('dev')); + +main.get('/', function (req, res) { + res.send('Hello from main app!') +}); + +main.get('/:sub', function (req, res) { + res.send('requsted ' + req.params.sub); +}); + +// Redirect app + +var redirect = express(); + +redirect.all('*', function (req, res) { + console.log(req.subdomains); + res.redirect('http://example.com:3000/' + req.subdomains[0]); +}); + +app.use(express.vhost('*.example.com', redirect)) +app.use(express.vhost('example.com', main)); + +app.listen(3000); +console.log('Express app started on port 3000'); + +//////////////////// + +// create an error with .status. we +// can then use the property in our +// custom error handler (Connect repects this prop as well) + +function merror(status, msg) { + var err = new Error(msg); + err.status = status; + return err; +} + +// if we wanted to supply more than JSON, we could +// use something similar to the content-negotiation +// example. + +// here we validate the API key, +// by mounting this middleware to /api +// meaning only paths prefixed with "/api" +// will cause this middleware to be invoked + +app.use('/api', function (req, res, next) { + var key = req.query['api-key']; + + // key isnt present + if (!key) return next(merror(400, 'api key required')); + + // key is invalid + if (!~apiKeys.indexOf(key)) return next(merror(401, 'invalid api key')); + + // all good, store req.key for route access + req.key = key; + next(); +}); + +// position our routes above the error handling middleware, +// and below our API middleware, since we want the API validation +// to take place BEFORE our routes +app.use(app.router); + +// middleware with an arity of 4 are considered +// error handling middleware. When you next(err) +// it will be passed through the defined middleware +// in order, but ONLY those with an arity of 4, ignoring +// regular middleware. +app.use(function (err, req, res, next) { + // whatever you want here, feel free to populate + // properties on `err` to treat it differently in here. + res.send(err.status || 500, { error: err.message }); +}); + +// our custom JSON 404 middleware. Since it's placed last +// it will be the last middleware called, if all others +// invoke next() and do not respond. +app.use(function (req, res) { + res.send(404, { error: "Lame, can't find that" }); +}); + +// map of valid api keys, typically mapped to +// account info with some sort of database like redis. +// api keys do _not_ serve as authentication, merely to +// track API usage or help prevent malicious behavior etc. + +var apiKeys = ['foo', 'bar', 'baz']; + +// these two objects will serve as our faux database + +var repos = [ + { name: 'express', url: 'http://github.com/visionmedia/express' } + , { name: 'stylus', url: 'http://github.com/learnboost/stylus' } + , { name: 'cluster', url: 'http://github.com/learnboost/cluster' } +]; + +var userRepos = { + tobi: [repos[0], repos[1]] + , loki: [repos[1]] + , jane: [repos[2]] +}; + +// we now can assume the api key is valid, +// and simply expose the data + +app.get('/api/users', function (req, res, next) { + res.send(users); +}); + +app.get('/api/repos', function (req, res, next) { + res.send(repos); +}); + +app.get('/api/user/:name/repos', function (req, res, next) { + var name = req.params.name + , user = userRepos[name]; + + if (user) res.send(user); + else next(); +}); + +if (!module.parent) { + app.listen(3000); + console.log('Express server listening on port 3000'); +} + +////// function test_general() { @@ -97,7 +1337,7 @@ function test_general() { } function test_request() { - var req: Express.ServerRequest; + var req: ExpressServerRequest; req.params.name; req.params[0]; req.query.q; @@ -130,7 +1370,7 @@ function test_request() { } function test_response() { - var res: Express.ServerResponse; + var res: ExpressServerResponse; res.status(404).sendfile('path/to/404.png'); res.set('Content-Type', 'text/plain'); res.set({ diff --git a/express/express.d.ts b/express/express.d.ts index c8fe6bec3..8e19ab004 100644 --- a/express/express.d.ts +++ b/express/express.d.ts @@ -1,218 +1,1954 @@ -// Type definitions for Express 3.0 +// Type definitions for Express 3.1 // Project: http://expressjs.com // Definitions by: Boris Yankov -// Definitions: https://github.com/borisyankov/DefinitelyTyped +// DefinitelyTyped: https://github.com/borisyankov/DefinitelyTyped -/* -USAGE +/* =================== USAGE =================== -/// -import express = module('express') -var app = express() -... + import express = module('express'); + var app = express(); -MIDDLEWARE + =============================================== */ -express exports lots of middleware, like .static, .session, etc, but use connect so you don't have to escape -var connect = require('connect') -connect.session({}) +/// -*/ +interface Route { + path: string; -/// + method: string; -// do not reference this. use module('express') instead -declare module _express { + callbacks: Function[]; - import http = module("http"); + regexp: any; - export interface Handler { - (req: ServerRequest, res: ServerResponse, next?: Function): void; - } + /** + * Check if this route matches `path`, if so + * populate `.params`. + */ + match(path: string): bool; +} +declare var Route: { + /** + * Initialize `Route` with the given HTTP `method`, `path`, + * and an array of `callbacks` and `options`. + * + * Options: + * + * - `sensitive` enable case-sensitive routes + * - `strict` enable strict matching for trailing slashes + * + * @param method + * @param path + * @param callbacks + * @param options + */ + new (method: string, path: string, callbacks: Function[], options: any): Route; +} - export interface Errback { (err: Error): void; } +interface Handler { + (req: ExpressServerRequest, res: ExpressServerResponse, next?: Function): void; +} - export interface CookieOptions { - maxAge?: number; - signed?: bool; - expires?: Date; - httpOnly?: bool; - path?: string; - domain?: string; - secure?: bool; - } +interface CookieOptions { + maxAge?: number; + signed?: bool; + expires?: Date; + httpOnly?: bool; + path?: string; + domain?: string; + secure?: bool; +} - export interface ExpressSettings { - env?: string; - views?: string; - } +interface Errback { (err: Error): void; } - export interface ServerApplication { - settings: ExpressSettings; - locals: any; - routes: any; +interface ExpressSession { + /** + * Update reset `.cookie.maxAge` to prevent + * the cookie from expiring when the + * session is still active. + * + * @return {Session} for chaining + * @api public + */ + touch(): ExpressSession; - (): ServerApplication; + /** + * Reset `.maxAge` to `.originalMaxAge`. + */ + resetMaxAge(): ExpressSession; - router: Handler; + /** + * Save the session data with optional callback `fn(err)`. + */ + save(fn: Function): ExpressSession; - use(route: string, callback: Function): ServerApplication; - use(route: string, server: ServerApplication): ServerApplication; - use(callback: Function): ServerApplication; - use(server: ServerApplication): ServerApplication; + /** + * Re-loads the session data _without_ altering + * the maxAge properties. Invokes the callback `fn(err)`, + * after which time if no exception has occurred the + * `req.session` property will be a new `Session` object, + * although representing the same session. + */ + reload(fn: Function): ExpressSession; - engine(ext: string, callback: Function): ServerApplication; + /** + * Destroy `this` session. + */ + destroy(fn: Function): ExpressSession; - param(param: Function): ServerApplication; - param(name: string, callback: Function): ServerApplication; - param(name: string, expressParam: any): ServerApplication; - param(name: any[], callback: Function): ServerApplication; + /** + * Regenerate this request's session. + */ + regenerate(fn: Function): ExpressSession; - set(name: string): ServerApplication; - set(name: string, val: any): ServerApplication; + user: any; - enabled(name: string): bool; - disabled(name: string): bool; + error: string; - enable(name: string): ServerApplication; - disable(name: string): ServerApplication; + success: string; - configure(env: string, callback: () => void ): ServerApplication; - configure(...params: any[]): ServerApplication; // covering this case: (...env: string[], callback: () => void) - configure(callback: () => void ): ServerApplication; + views: any; +} +declare var ExpressSession: { + /** + * Create a new `Session` with the given request and `data`. + */ + new (req: ExpressServerRequest, data: any): ExpressSession; +} - all(path: string, ...callbacks: Function[]): void; +interface ExpressServerRequest { - render(view: string, callback: (err: Error, html) => void ): void; - render(view: string, optionss: any, callback: (err: Error, html) => void ): void; + session: ExpressSession; - listen(port: number, hostname: string, backlog: number, callback: Function): void; - listen(port: number, callback: Function): void; - listen(path: string, callback?: Function): void; - listen(handle: any, listeningListener?: Function): void; + /** + * Return request header. + * + * The `Referrer` header field is special-cased, + * both `Referrer` and `Referer` are interchangeable. + * + * Examples: + * + * req.get('Content-Type'); + * // => "text/plain" + * + * req.get('content-type'); + * // => "text/plain" + * + * req.get('Something'); + * // => undefined + * + * Aliased as `req.header()`. + * + * @param name + */ + get (name: string): string; - get(name: string): any; - get(path: RegExp, handler: Handler): void; - get(path: string, ...callbacks: Handler[]): void; + header(name: string): string; - post(path: RegExp, handler: Handler ): void; - post(path: string, ...callbacks: Handler[]): void; + /** + * Check if the given `type(s)` is acceptable, returning + * the best match when true, otherwise `undefined`, in which + * case you should respond with 406 "Not Acceptable". + * + * The `type` value may be a single mime type string + * such as "application/json", the extension name + * such as "json", a comma-delimted list such as "json, html, text/plain", + * or an array `["json", "html", "text/plain"]`. When a list + * or array is given the _best_ match, if any is returned. + * + * Examples: + * + * // Accept: text/html + * req.accepts('html'); + * // => "html" + * + * // Accept: text/*, application/json + * req.accepts('html'); + * // => "html" + * req.accepts('text/html'); + * // => "text/html" + * req.accepts('json, text'); + * // => "json" + * req.accepts('application/json'); + * // => "application/json" + * + * // Accept: text/*, application/json + * req.accepts('image/png'); + * req.accepts('png'); + * // => undefined + * + * // Accept: text/*;q=.5, application/json + * req.accepts(['html', 'json']); + * req.accepts('html, json'); + * // => "json" + */ + accepts(type: string): string; - put(path: RegExp, handler: Handler ): void; - put(path: string, ...callbacks: Handler[]): void; + accepts(type: string[]): string; - del(path: RegExp, handler: Handler ): void; - del(path: string, ...callbacks: Handler[]): void; - } + /** + * Check if the given `charset` is acceptable, + * otherwise you should respond with 406 "Not Acceptable". + * + * @param charset + */ + acceptsCharset(charset: string): bool; - export interface ServerRequest extends http.ServerRequest { + /** + * Check if the given `lang` is acceptable, + * otherwise you should respond with 406 "Not Acceptable". + * + * @param lang + */ + acceptsLanguage(lang: string): bool; - accepted: any[]; - acceptedLanguages: string[]; - acceptedCharsets: string[]; + /** + * Parse Range header field, + * capping to the given `size`. + * + * Unspecified ranges such as "0-" require + * knowledge of your resource length. In + * the case of a byte range this is of course + * the total number of bytes. If the Range + * header field is not given `null` is returned, + * `-1` when unsatisfiable, `-2` when syntactically invalid. + * + * NOTE: remember that ranges are inclusive, so + * for example "Range: users=0-3" should respond + * with 4 users when available, not 3. + * + * @param size + */ + range(size: number): Array; - params: any; - query: any; - body: any; - files: any; + /** + * Return an array of Accepted media types + * ordered from highest quality to lowest. + * + * Examples: + * + * [ { value: 'application/json', + * quality: 1, + * type: 'application', + * subtype: 'json' }, + * { value: 'text/html', + * quality: 0.5, + * type: 'text', + * subtype: 'html' } ] + */ + accepted: Array; - route: any; - cookies: any; - signedCookies: any; + /** + * Return an array of Accepted languages + * ordered from highest quality to lowest. + * + * Examples: + * + * Accept-Language: en;q=.5, en-us + * ['en-us', 'en'] + */ + acceptedLanguages: Array; - get(field: string): string; - header(field: string): string; + /** + * Return an array of Accepted charsets + * ordered from highest quality to lowest. + * + * Examples: + * + * Accept-Charset: iso-8859-5;q=.2, unicode-1-1;q=0.8 + * ['unicode-1-1', 'iso-8859-5'] + */ + acceptedCharsets: Array; - accepts(types: string): any; - accepts(types: string[]): any; - acceptsCharset(charset: string): bool; - acceptsLanguage(lang: string): bool; + /** + * Return the value of param `name` when present or `defaultValue`. + * + * - Checks route placeholders, ex: _/user/:id_ + * - Checks body params, ex: id=12, {"id":12} + * - Checks query string params, ex: ?id=12 + * + * To utilize request bodies, `req.body` + * should be an object. This can be done by using + * the `connect.bodyParser()` middleware. + * + * @param name + * @param defaultValue + */ + param(name: string, defaultValue?: any): string; - range(size: number): number[]; + /** + * Check if the incoming request contains the "Content-Type" + * header field, and it contains the give mime `type`. + * + * Examples: + * + * // With Content-Type: text/html; charset=utf-8 + * req.is('html'); + * req.is('text/html'); + * req.is('text/*'); + * // => true + * + * // When Content-Type is application/json + * req.is('json'); + * req.is('application/json'); + * req.is('application/*'); + * // => true + * + * req.is('html'); + * // => false + * + * @param type + */ + is(type: string): bool; - param(name: string, defaultValue?: any): string; - is(type: string): bool; + /** + * Return the protocol string "http" or "https" + * when requested with TLS. When the "trust proxy" + * setting is enabled the "X-Forwarded-Proto" header + * field will be trusted. If you're running behind + * a reverse proxy that supplies https for you this + * may be enabled. + */ + protocol: string; - protocol: string; - secure: bool; - ip: string; - ips: string[]; - auth: any; - subdomains: string[]; - path: string; - host: string; - fresh: bool; - stale: bool; - xhr: bool; - } + /** + * Short-hand for: + * + * req.protocol == 'https' + */ + secure: bool; - export interface ServerResponse extends http.ServerResponse { + /** + * Return the remote address, or when + * "trust proxy" is `true` return + * the upstream addr. + */ + ip: string; - charset: string; - locals: any; + /** + * When "trust proxy" is `true`, parse + * the "X-Forwarded-For" ip address list. + * + * For example if the value were "client, proxy1, proxy2" + * you would receive the array `["client", "proxy1", "proxy2"]` + * where "proxy2" is the furthest down-stream. + */ + ips: string[]; - status(code: number): ServerResponse; - links(links: any): ServerResponse; + /** + * Return basic auth credentials. + * + * Examples: + * + * // http://tobi:hello@example.com + * req.auth + * // => { username: 'tobi', password: 'hello' } + */ + auth: any; - send(status: number): ServerResponse; - send(bodyOrStatus: any): ServerResponse; - send(status: number, body: any): ServerResponse; - json(status: number): ServerResponse; - json(bodyOrStatus: any): ServerResponse; - json(status: number, body: any): ServerResponse; - jsonp(status: number): ServerResponse; - jsonp(bodyOrStatus: any): ServerResponse; - jsonp(status: number, body: any): ServerResponse; + /** + * Return subdomains as an array. + * + * Subdomains are the dot-separated parts of the host before the main domain of + * the app. By default, the domain of the app is assumed to be the last two + * parts of the host. This can be changed by setting "subdomain offset". + * + * For example, if the domain is "tobi.ferrets.example.com": + * If "subdomain offset" is not set, req.subdomains is `["ferrets", "tobi"]`. + * If "subdomain offset" is 3, req.subdomains is `["tobi"]`. + */ + subdomains: string[]; - sendfile(path: string): void; - sendfile(path: string, options: any): void; - sendfile(path: string, fn: Errback): void; - sendfile(path: string, options: any, fn: Errback): void; - download(path: string): void; - download(path: string, filename: string): void; - download(path: string, fn: Errback): void; - download(path: string, filename: string, fn: Errback): void; + /** + * Short-hand for `url.parse(req.url).pathname`. + */ + path: string; - type(type: string): ServerResponse; - contentType(type: string): ServerResponse; + /** + * Parse the "Host" header field hostname. + */ + host: string; - format(object: any): ServerResponse; - attachment(filename?: string): ServerResponse; + /** + * Check if the request is fresh, aka + * Last-Modified and/or the ETag + * still match. + */ + fresh: bool; - set(field: any): void; - set(field: string, value: string): void; - header(field: any): void; - header(field: string, value: string): void; + /** + * Check if the request is stale, aka + * "Last-Modified" and / or the "ETag" for the + * resource has changed. + */ + stale: bool; - get(field: string): string; + /** + * Check if the request was an _XMLHttpRequest_. + */ + xhr: bool; - clearCookie(name: string, options?: any): ServerResponse; - cookie(name: string, value: any, options?: CookieOptions): ServerResponse; + //body: { username: string; password: string; remember: bool; title: string; }; + body: any; - redirect(url: string): void; - redirect(status: number, url: string): void; - redirect(url: string, status: number): void; + //cookies: { string; remember: bool; }; + cookies: any; - render(view: string, options: any): void; - render(view: string, callback: (err: Error, html: any) => void ): void; - render(view: string, options: any, callback: (err: Error, html: any) => void ): void; - } + method: string; + + params: any; + + user: any; + + files: any; + + /** + * Clear cookie `name`. + * + * @param name + * @param options + */ + clearCookie(name: string, options?: any): ExpressServerResponse; + + query: any; + + route: any; + + signedCookies: any; + + originalUrl: string; +} + +interface ExpressServerResponse { + /** + * Set status `code`. + * + * @param code + */ + status(code: number): ExpressServerResponse; + + /** + * Set Link header field with the given `links`. + * + * Examples: + * + * res.links({ + * next: 'http://api.example.com/users?page=2', + * last: 'http://api.example.com/users?page=5' + * }); + * + * @param links + */ + links(links: any): ExpressServerResponse; + + /** + * Send a response. + * + * Examples: + * + * res.send(new Buffer('wahoo')); + * res.send({ some: 'json' }); + * res.send('

some html

'); + * res.send(404, 'Sorry, cant find that'); + * res.send(404); + */ + send(status: number): ExpressServerResponse; + + send(bodyOrStatus: any): ExpressServerResponse; + + send(status: number, body: any): ExpressServerResponse; + + + /** + * Send JSON response. + * + * Examples: + * + * res.json(null); + * res.json({ user: 'tj' }); + * res.json(500, 'oh noes!'); + * res.json(404, 'I dont have that'); + */ + json(status: number): ExpressServerResponse; + + json(bodyOrStatus: any): ExpressServerResponse; + + json(status: number, body: any): ExpressServerResponse; + + /** + * Send JSON response with JSONP callback support. + * + * Examples: + * + * res.jsonp(null); + * res.jsonp({ user: 'tj' }); + * res.jsonp(500, 'oh noes!'); + * res.jsonp(404, 'I dont have that'); + */ + jsonp(status: number): ExpressServerResponse; + + jsonp(bodyOrStatus: any): ExpressServerResponse; + + jsonp(status: number, body: any): ExpressServerResponse; + + /** + * Transfer the file at the given `path`. + * + * Automatically sets the _Content-Type_ response header field. + * The callback `fn(err)` is invoked when the transfer is complete + * or when an error occurs. Be sure to check `res.sentHeader` + * if you wish to attempt responding, as the header and some data + * may have already been transferred. + * + * Options: + * + * - `maxAge` defaulting to 0 + * - `root` root directory for relative filenames + * + * Examples: + * + * The following example illustrates how `res.sendfile()` may + * be used as an alternative for the `static()` middleware for + * dynamic situations. The code backing `res.sendfile()` is actually + * the same code, so HTTP cache support etc is identical. + * + * app.get('/user/:uid/photos/:file', function(req, res){ + * var uid = req.params.uid + * , file = req.params.file; + * + * req.user.mayViewFilesFrom(uid, function(yes){ + * if (yes) { + * res.sendfile('/uploads/' + uid + '/' + file); + * } else { + * res.send(403, 'Sorry! you cant see that.'); + * } + * }); + * }); + */ + sendfile(path: string): void; + + sendfile(path: string, options: any): void; + + sendfile(path: string, fn: Errback): void; + + sendfile(path: string, options: any, fn: Errback): void; + + /** + * Transfer the file at the given `path` as an attachment. + * + * Optionally providing an alternate attachment `filename`, + * and optional callback `fn(err)`. The callback is invoked + * when the data transfer is complete, or when an error has + * ocurred. Be sure to check `res.headerSent` if you plan to respond. + * + * This method uses `res.sendfile()`. + */ + download(path: string): void; + + download(path: string, filename: string): void; + + download(path: string, fn: Errback): void; + + download(path: string, filename: string, fn: Errback): void; + + /** + * Set _Content-Type_ response header with `type` through `mime.lookup()` + * when it does not contain "/", or set the Content-Type to `type` otherwise. + * + * Examples: + * + * res.type('.html'); + * res.type('html'); + * res.type('json'); + * res.type('application/json'); + * res.type('png'); + * + * @param type + */ + contentType(type: string): ExpressServerResponse; + + /** + * Set _Content-Type_ response header with `type` through `mime.lookup()` + * when it does not contain "/", or set the Content-Type to `type` otherwise. + * + * Examples: + * + * res.type('.html'); + * res.type('html'); + * res.type('json'); + * res.type('application/json'); + * res.type('png'); + * + * @param type + */ + type(type: string): ExpressServerResponse; + + /** + * Respond to the Acceptable formats using an `obj` + * of mime-type callbacks. + * + * This method uses `req.accepted`, an array of + * acceptable types ordered by their quality values. + * When "Accept" is not present the _first_ callback + * is invoked, otherwise the first match is used. When + * no match is performed the server responds with + * 406 "Not Acceptable". + * + * Content-Type is set for you, however if you choose + * you may alter this within the callback using `res.type()` + * or `res.set('Content-Type', ...)`. + * + * res.format({ + * 'text/plain': function(){ + * res.send('hey'); + * }, + * + * 'text/html': function(){ + * res.send('

hey

'); + * }, + * + * 'appliation/json': function(){ + * res.send({ message: 'hey' }); + * } + * }); + * + * In addition to canonicalized MIME types you may + * also use extnames mapped to these types: + * + * res.format({ + * text: function(){ + * res.send('hey'); + * }, + * + * html: function(){ + * res.send('

hey

'); + * }, + * + * json: function(){ + * res.send({ message: 'hey' }); + * } + * }); + * + * By default Express passes an `Error` + * with a `.status` of 406 to `next(err)` + * if a match is not made. If you provide + * a `.default` callback it will be invoked + * instead. + * + * @param obj + */ + format(obj: any): ExpressServerResponse; + + /** + * Set _Content-Disposition_ header to _attachment_ with optional `filename`. + * + * @param filename + */ + attachment(filename?: string): ExpressServerResponse; + + /** + * Set header `field` to `val`, or pass + * an object of header fields. + * + * Examples: + * + * res.set('Foo', ['bar', 'baz']); + * res.set('Accept', 'application/json'); + * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' }); + * + * Aliased as `res.header()`. + */ + set (field: any): void; + + set (field: string, value?: string): void; + + header(field: any): void; + + header(field: string, value?: string): void; + + /** + * Get value for header `field`. + * + * @param field + */ + get (field: string): string; + + /** + * Clear cookie `name`. + * + * @param name + * @param options + */ + clearCookie(name: string, options?: any): ExpressServerResponse; + + /** + * Set cookie `name` to `val`, with the given `options`. + * + * Options: + * + * - `maxAge` max-age in milliseconds, converted to `expires` + * - `signed` sign the cookie + * - `path` defaults to "/" + * + * Examples: + * + * // "Remember Me" for 15 minutes + * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true }); + * + * // save as above + * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true }) + */ + cookie(name: string, val: string, options: CookieOptions); + + cookie(name: string, val: any, options: CookieOptions); + + cookie(name: string, val: any); + + /** + * Set the location header to `url`. + * + * The given `url` can also be the name of a mapped url, for + * example by default express supports "back" which redirects + * to the _Referrer_ or _Referer_ headers or "/". + * + * Examples: + * + * res.location('/foo/bar').; + * res.location('http://example.com'); + * res.location('../login'); // /blog/post/1 -> /blog/login + * + * Mounting: + * + * When an application is mounted and `res.location()` + * is given a path that does _not_ lead with "/" it becomes + * relative to the mount-point. For example if the application + * is mounted at "/blog", the following would become "/blog/login". + * + * res.location('login'); + * + * While the leading slash would result in a location of "/login": + * + * res.location('/login'); + * + * @param url + */ + location(url: string); + + /** + * Redirect to the given `url` with optional response `status` + * defaulting to 302. + * + * The resulting `url` is determined by `res.location()`, so + * it will play nicely with mounted apps, relative paths, + * `"back"` etc. + * + * Examples: + * + * res.redirect('/foo/bar'); + * res.redirect('http://example.com'); + * res.redirect(301, 'http://example.com'); + * res.redirect('http://example.com', 301); + * res.redirect('../login'); // /blog/post/1 -> /blog/login + */ + redirect(url: string): void; + + redirect(status: number, url: string): void; + + redirect(url: string, status: number): void; + + /** + * Render `view` with the given `options` and optional callback `fn`. + * When a callback function is given a response will _not_ be made + * automatically, otherwise a response of _200_ and _text/html_ is given. + * + * Options: + * + * - `cache` boolean hinting to the engine it should cache + * - `filename` filename of the view being rendered + */ + render(view: string): void; + + render(view: string, options: any): void; + + render(view: string, callback: (err: Error, html: any) => void ): void; + + render(view: string, options: any, callback: (err: Error, html: any) => void ): void; + + locals: any; + + charset: string; +} + +interface ExpressApplication { + /** + * Initialize the server. + * + * - setup default configuration + * - setup default middleware + * - setup route reflection methods + */ + init(); + + /** + * Initialize application configuration. + */ + defaultConfiguration(); + + /** + * Proxy `connect#use()` to apply settings to + * mounted applications. + **/ + use(route: string, callback?: Function): ExpressApplication; + + use(route: string, server: ExpressApplication): ExpressApplication; + + use(callback: Function): ExpressApplication; + + use(server: ExpressApplication): ExpressApplication; + + /** + * Register the given template engine callback `fn` + * as `ext`. + * + * By default will `require()` the engine based on the + * file extension. For example if you try to render + * a "foo.jade" file Express will invoke the following internally: + * + * app.engine('jade', require('jade').__express); + * + * For engines that do not provide `.__express` out of the box, + * or if you wish to "map" a different extension to the template engine + * you may use this method. For example mapping the EJS template engine to + * ".html" files: + * + * app.engine('html', require('ejs').renderFile); + * + * In this case EJS provides a `.renderFile()` method with + * the same signature that Express expects: `(path, options, callback)`, + * though note that it aliases this method as `ejs.__express` internally + * so if you're using ".ejs" extensions you dont need to do anything. + * + * Some template engines do not follow this convention, the + * [Consolidate.js](https://github.com/visionmedia/consolidate.js) + * library was created to map all of node's popular template + * engines to follow this convention, thus allowing them to + * work seamlessly within Express. + */ + engine(ext: string, fn: Function): ExpressApplication; + + /** + * Map the given param placeholder `name`(s) to the given callback(s). + * + * Parameter mapping is used to provide pre-conditions to routes + * which use normalized placeholders. For example a _:user_id_ parameter + * could automatically load a user's information from the database without + * any additional code, + * + * The callback uses the samesignature as middleware, the only differencing + * being that the value of the placeholder is passed, in this case the _id_ + * of the user. Once the `next()` function is invoked, just like middleware + * it will continue on to execute the route, or subsequent parameter functions. + * + * app.param('user_id', function(req, res, next, id){ + * User.find(id, function(err, user){ + * if (err) { + * next(err); + * } else if (user) { + * req.user = user; + * next(); + * } else { + * next(new Error('failed to load user')); + * } + * }); + * }); + * + * @param name + * @param fn + */ + param(name: string, fn: Function): ExpressApplication; + + param(name: Array, fn: Function): ExpressApplication; + + /** + * Assign `setting` to `val`, or return `setting`'s value. + * + * app.set('foo', 'bar'); + * app.get('foo'); + * // => "bar" + * + * Mounted servers inherit their parent server's settings. + * + * @param setting + * @param val + */ + set (setting: string, val: string): ExpressApplication; + + /** + * Return the app's absolute pathname + * based on the parent(s) that have + * mounted it. + * + * For example if the application was + * mounted as "/admin", which itself + * was mounted as "/blog" then the + * return value would be "/blog/admin". + */ + path(): string; + + /** + * Check if `setting` is enabled (truthy). + * + * app.enabled('foo') + * // => false + * + * app.enable('foo') + * app.enabled('foo') + * // => true + */ + enabled(setting: string): bool; + + /** + * Check if `setting` is disabled. + * + * app.disabled('foo') + * // => true + * + * app.enable('foo') + * app.disabled('foo') + * // => false + * + * @param setting + */ + disabled(setting: string): bool; + + /** + * Enable `setting`. + * + * @param setting + */ + enable(setting: string): ExpressApplication; + + /** + * Disable `setting`. + * + * @param setting + */ + disable(setting: string): ExpressApplication; + + /** + * Configure callback for zero or more envs, + * when no `env` is specified that callback will + * be invoked for all environments. Any combination + * can be used multiple times, in any order desired. + * + * Examples: + * + * app.configure(function(){ + * // executed for all envs + * }); + * + * app.configure('stage', function(){ + * // executed staging env + * }); + * + * app.configure('stage', 'production', function(){ + * // executed for stage and production + * }); + * + * Note: + * + * These callbacks are invoked immediately, and + * are effectively sugar for the following: + * + * var env = process.env.NODE_ENV || 'development'; + * + * switch (env) { + * case 'development': + * ... + * break; + * case 'stage': + * ... + * break; + * case 'production': + * ... + * break; + * } + * + * @param env + * @param fn + */ + configure(env: string, fn: Function): ExpressApplication; + + configure(env0: string, env1: string, fn: Function): ExpressApplication; + + configure(env0: string, env1: string, env2: string, fn: Function): ExpressApplication; + + configure(env0: string, env1: string, env2: string, env3: string, fn: Function): ExpressApplication; + + configure(env0: string, env1: string, env2: string, env3: string, env4: string, fn: Function): ExpressApplication; + + configure(fn: Function): ExpressApplication; + + /** + * Special-cased "all" method, applying the given route `path`, + * middleware, and callback to _every_ HTTP method. + * + * @param path + * @param fn + */ + all(path: string, fn?: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): ExpressApplication; + + all(path: string, ...callbacks: Function[]): void; + + /** + * Render the given view `name` name with `options` + * and a callback accepting an error and the + * rendered template string. + * + * Example: + * + * app.render('email', { name: 'Tobi' }, function(err, html){ + * // ... + * }) + * + * @param name + * @param options or fn + * @param fn + */ + render(name: string, options: string, fn: Function); + + render(name: string, fn: Function); + + get (name: string): any; + + get (name: string, handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + get (name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + get (name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + get (name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + get (name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler5: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + get (name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler5: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + ...handlers: any[]): any; + + get (name: RegExp): any; + + get (name: RegExp, handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + get (name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + get (name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + get (name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + get (name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler5: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + ...handlers: any[]): any; + + post(name: string): any; + + post(name: string, handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + post(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + post(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + post(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + post(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler5: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + ...handlers: any[]): any; + + post(name: RegExp): any; + + post(name: RegExp, handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + post(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + post(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + post(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + post(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler5: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + ...handlers: any[]): any; + + put(name: string): any; + + put(name: string, handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + put(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + put(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + put(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + put(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler5: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + ...handlers: any[]): any; + + put(name: RegExp): any; + + put(name: RegExp, handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + put(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + put(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + put(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + put(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler5: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + ...handlers: any[]): any; + + del(name: string): any; + + del(name: string, handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + del(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + del(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + del(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + del(name: string, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler5: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + ...handlers: any[]): any; + + del(name: RegExp): any; + + del(name: RegExp, handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + del(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + del(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + del(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any): any; + + del(name: RegExp, + handler: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler2: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler3: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler4: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + handler5: (req: ExpressServerRequest, res: ExpressServerResponse, next: Function) => any, + ...handlers: any[]): any; + + /** + * Listen for connections. + * + * A node `http.Server` is returned, with this + * application (which is a `Function`) as its + * callback. If you wish to create both an HTTP + * and HTTPS server you may do so with the "http" + * and "https" modules as shown here: + * + * var http = require('http') + * , https = require('https') + * , express = require('express') + * , app = express(); + * + * http.createServer(app).listen(80); + * https.createServer({ ... }, app).listen(443); + */ + listen(port: number, hostname: string, backlog: number, callback: Function): void; + + listen(port: number, callback: Function): void; + + listen(path: string, callback?: Function): void; + + listen(handle: any, listeningListener?: Function): void; + + render(view: string, callback: (err: Error, html) => void ): void; + + render(view: string, optionss: any, callback: (err: Error, html) => void ): void; + + route: Route; + + router: string; + + settings: any; + + resource: any; + + map: any; + + locals: any; +} + +interface Express extends ExpressApplication { + /** + * Framework version. + */ + version: string; + + /** + * Expose mime. + */ + mime: string; + + (): ExpressApplication; + + /** + * Create an express application. + */ + createApplication(): ExpressApplication; + + createServer(): ExpressApplication; + + application: any; + + request: ExpressServerRequest; + + response: ExpressServerResponse; } declare module "express" { - export function (): _express.ServerApplication; - export function createServer(): ServerApplication; - export function static(path: string): any; - export var listen; + export function (): Express; - // Connect middleware + /** + * Body parser: + * + * Parse request bodies, supports _application/json_, + * _application/x-www-form-urlencoded_, and _multipart/form-data_. + * + * This is equivalent to: + * + * app.use(connect.json()); + * app.use(connect.urlencoded()); + * app.use(connect.multipart()); + * + * Examples: + * + * connect() + * .use(connect.bodyParser()) + * .use(function(req, res) { + * res.end('viewing user ' + req.body.user.name); + * }); + * + * $ curl -d 'user[name]=tj' http://local/ + * $ curl -d '{"user":{"name":"tj"}}' -H "Content-Type: application/json" http://local/ + * + * View [json](json.html), [urlencoded](urlencoded.html), and [multipart](multipart.html) for more info. + * + * @param options + */ export function bodyParser(options?: any): Handler; - export function errorHandler(opts?: any): Handler; - export function methodOverride(): Handler; - export interface ServerApplication extends _express.ServerApplication {} - export interface ServerRequest extends _express.ServerRequest {} - export interface ServerResponse extends _express.ServerResponse {} - export interface Handler extends _express.Handler {} -} + /** + * Error handler: + * + * Development error handler, providing stack traces + * and error message responses for requests accepting text, html, + * or json. + * + * Text: + * + * By default, and when _text/plain_ is accepted a simple stack trace + * or error message will be returned. + * + * JSON: + * + * When _application/json_ is accepted, connect will respond with + * an object in the form of `{ "error": error }`. + * + * HTML: + * + * When accepted connect will output a nice html stack trace. + */ + export function errorHandler(opts?: any): Handler; + + /** + * Method Override: + * + * Provides faux HTTP method support. + * + * Pass an optional `key` to use when checking for + * a method override, othewise defaults to _\_method_. + * The original method is available via `req.originalMethod`. + * + * @param key + */ + export function methodOverride(key?: string): Handler; + + /** + * Cookie parser: + * + * Parse _Cookie_ header and populate `req.cookies` + * with an object keyed by the cookie names. Optionally + * you may enabled signed cookie support by passing + * a `secret` string, which assigns `req.secret` so + * it may be used by other middleware. + * + * Examples: + * + * connect() + * .use(connect.cookieParser('optional secret string')) + * .use(function(req, res, next){ + * res.end(JSON.stringify(req.cookies)); + * }) + * + * @param secret + */ + export function cookieParser(secret?: string): Handler; + + /** + * Session: + * + * Setup session store with the given `options`. + * + * Session data is _not_ saved in the cookie itself, however + * cookies are used, so we must use the [cookieParser()](cookieParser.html) + * middleware _before_ `session()`. + * + * Examples: + * + * connect() + * .use(connect.cookieParser()) + * .use(connect.session({ secret: 'keyboard cat', key: 'sid', cookie: { secure: true }})) + * + * Options: + * + * - `key` cookie name defaulting to `connect.sid` + * - `store` session store instance + * - `secret` session cookie is signed with this secret to prevent tampering + * - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }` + * - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto") + * + * Cookie option: + * + * By default `cookie.maxAge` is `null`, meaning no "expires" parameter is set + * so the cookie becomes a browser-session cookie. When the user closes the + * browser the cookie (and session) will be removed. + * + * ## req.session + * + * To store or access session data, simply use the request property `req.session`, + * which is (generally) serialized as JSON by the store, so nested objects + * are typically fine. For example below is a user-specific view counter: + * + * connect() + * .use(connect.favicon()) + * .use(connect.cookieParser()) + * .use(connect.session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }})) + * .use(function(req, res, next){ + * var sess = req.session; + * if (sess.views) { + * res.setHeader('Content-Type', 'text/html'); + * res.write('

views: ' + sess.views + '

'); + * res.write('

expires in: ' + (sess.cookie.maxAge / 1000) + 's

'); + * res.end(); + * sess.views++; + * } else { + * sess.views = 1; + * res.end('welcome to the session demo. refresh!'); + * } + * } + * )).listen(3000); + * + * ## Session#regenerate() + * + * To regenerate the session simply invoke the method, once complete + * a new SID and `Session` instance will be initialized at `req.session`. + * + * req.session.regenerate(function(err){ + * // will have a new session here + * }); + * + * ## Session#destroy() + * + * Destroys the session, removing `req.session`, will be re-generated next request. + * + * req.session.destroy(function(err){ + * // cannot access session here + * }); + * + * ## Session#reload() + * + * Reloads the session data. + * + * req.session.reload(function(err){ + * // session updated + * }); + * + * ## Session#save() + * + * Save the session. + * + * req.session.save(function(err){ + * // session saved + * }); + * + * ## Session#touch() + * + * Updates the `.maxAge` property. Typically this is + * not necessary to call, as the session middleware does this for you. + * + * ## Session#cookie + * + * Each session has a unique cookie object accompany it. This allows + * you to alter the session cookie per visitor. For example we can + * set `req.session.cookie.expires` to `false` to enable the cookie + * to remain for only the duration of the user-agent. + * + * ## Session#maxAge + * + * Alternatively `req.session.cookie.maxAge` will return the time + * remaining in milliseconds, which we may also re-assign a new value + * to adjust the `.expires` property appropriately. The following + * are essentially equivalent + * + * var hour = 3600000; + * req.session.cookie.expires = new Date(Date.now() + hour); + * req.session.cookie.maxAge = hour; + * + * For example when `maxAge` is set to `60000` (one minute), and 30 seconds + * has elapsed it will return `30000` until the current request has completed, + * at which time `req.session.touch()` is called to reset `req.session.maxAge` + * to its original value. + * + * req.session.cookie.maxAge; + * // => 30000 + * + * Session Store Implementation: + * + * Every session store _must_ implement the following methods + * + * - `.get(sid, callback)` + * - `.set(sid, session, callback)` + * - `.destroy(sid, callback)` + * + * Recommended methods include, but are not limited to: + * + * - `.length(callback)` + * - `.clear(callback)` + * + * For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo. + * + * @param options + */ + export function session(options?: any): Handler; + + /** + * Hash the given `sess` object omitting changes + * to `.cookie`. + * + * @param sess + */ + export function hash(sess: string): string; + + /** + * Static: + * + * Static file server with the given `root` path. + * + * Examples: + * + * var oneDay = 86400000; + * + * connect() + * .use(connect.static(__dirname + '/public')) + * + * connect() + * .use(connect.static(__dirname + '/public', { maxAge: oneDay })) + * + * Options: + * + * - `maxAge` Browser cache maxAge in milliseconds. defaults to 0 + * - `hidden` Allow transfer of hidden files. defaults to false + * - `redirect` Redirect to trailing "/" when the pathname is a dir. defaults to true + * + * @param root + * @param options + */ + export function static (root: string, options?: any): Handler; + + /** + * Basic Auth: + * + * Enfore basic authentication by providing a `callback(user, pass)`, + * which must return `true` in order to gain access. Alternatively an async + * method is provided as well, invoking `callback(user, pass, callback)`. Populates + * `req.user`. The final alternative is simply passing username / password + * strings. + * + * Simple username and password + * + * connect(connect.basicAuth('username', 'password')); + * + * Callback verification + * + * connect() + * .use(connect.basicAuth(function(user, pass){ + * return 'tj' == user & 'wahoo' == pass; + * })) + * + * Async callback verification, accepting `fn(err, user)`. + * + * connect() + * .use(connect.basicAuth(function(user, pass, fn){ + * User.authenticate({ user: user, pass: pass }, fn); + * })) + * + * @param callback or username + * @param realm + */ + export function basicAuth(callback: Function, realm: string); + + export function basicAuth(callback: string, realm: string); + + export function basicAuth(callback: Function); + + /** + * Compress: + * + * Compress response data with gzip/deflate. + * + * Filter: + * + * A `filter` callback function may be passed to + * replace the default logic of: + * + * exports.filter = function(req, res){ + * return /json|text|javascript/.test(res.getHeader('Content-Type')); + * }; + * + * Options: + * + * All remaining options are passed to the gzip/deflate + * creation functions. Consult node's docs for additional details. + * + * - `chunkSize` (default: 16*1024) + * - `windowBits` + * - `level`: 0-9 where 0 is no compression, and 9 is slow but best compression + * - `memLevel`: 1-9 low is slower but uses less memory, high is fast but uses more + * - `strategy`: compression strategy + * + * @param options + */ + export function compress(options?: any): Handler; + + /** + * Cookie Session: + * + * Cookie session middleware. + * + * var app = connect(); + * app.use(connect.cookieParser()); + * app.use(connect.cookieSession({ secret: 'tobo!', cookie: { maxAge: 60 * 60 * 1000 }})); + * + * Options: + * + * - `key` cookie name defaulting to `connect.sess` + * - `secret` prevents cookie tampering + * - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }` + * - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto") + * + * Clearing sessions: + * + * To clear the session simply set its value to `null`, + * `cookieSession()` will then respond with a 1970 Set-Cookie. + * + * req.session = null; + * + * @param options + */ + export function cookieSession(options?: any): Handler; + + /** + * Anti CSRF: + * + * CRSF protection middleware. + * + * By default this middleware generates a token named "_csrf" + * which should be added to requests which mutate + * state, within a hidden form field, query-string etc. This + * token is validated against the visitor's `req.session._csrf` + * property. + * + * The default `value` function checks `req.body` generated + * by the `bodyParser()` middleware, `req.query` generated + * by `query()`, and the "X-CSRF-Token" header field. + * + * This middleware requires session support, thus should be added + * somewhere _below_ `session()` and `cookieParser()`. + * + * Options: + * + * - `value` a function accepting the request, returning the token + * + * @param options + */ + export function csrf(options: any); + + /** + * Directory: + * + * Serve directory listings with the given `root` path. + * + * Options: + * + * - `hidden` display hidden (dot) files. Defaults to false. + * - `icons` display icons. Defaults to false. + * - `filter` Apply this filter function to files. Defaults to false. + * + * @param root + * @param options + */ + export function directory(root: string, options?: any): Handler; + + /** + * Favicon: + * + * By default serves the connect favicon, or the favicon + * located by the given `path`. + * + * Options: + * + * - `maxAge` cache-control max-age directive, defaulting to 1 day + * + * Examples: + * + * Serve default favicon: + * + * connect() + * .use(connect.favicon()) + * + * Serve favicon before logging for brevity: + * + * connect() + * .use(connect.favicon()) + * .use(connect.logger('dev')) + * + * Serve custom favicon: + * + * connect() + * .use(connect.favicon('public/favicon.ico)) + * + * @param path + * @param options + */ + export function favicon(path?: string, options?: any); + + /** + * JSON: + * + * Parse JSON request bodies, providing the + * parsed object as `req.body`. + * + * Options: + * + * - `strict` when `false` anything `JSON.parse()` accepts will be parsed + * - `reviver` used as the second "reviver" argument for JSON.parse + * - `limit` byte limit disabled by default + * + * @param options + */ + export function json(options?: any): Handler; + + /** + * Limit: + * + * Limit request bodies to the given size in `bytes`. + * + * A string representation of the bytesize may also be passed, + * for example "5mb", "200kb", "1gb", etc. + * + * connect() + * .use(connect.limit('5.5mb')) + * .use(handleImageUpload) + */ + export function limit(bytes: number): Handler; + + export function limit(bytes: string): Handler; + + /** + * Logger: + * + * Log requests with the given `options` or a `format` string. + * + * Options: + * + * - `format` Format string, see below for tokens + * - `stream` Output stream, defaults to _stdout_ + * - `buffer` Buffer duration, defaults to 1000ms when _true_ + * - `immediate` Write log line on request instead of response (for response times) + * + * Tokens: + * + * - `:req[header]` ex: `:req[Accept]` + * - `:res[header]` ex: `:res[Content-Length]` + * - `:http-version` + * - `:response-time` + * - `:remote-addr` + * - `:date` + * - `:method` + * - `:url` + * - `:referrer` + * - `:user-agent` + * - `:status` + * + * Formats: + * + * Pre-defined formats that ship with connect: + * + * - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"' + * - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms' + * - `tiny` ':method :url :status :res[content-length] - :response-time ms' + * - `dev` concise output colored by response status for development use + * + * Examples: + * + * connect.logger() // default + * connect.logger('short') + * connect.logger('tiny') + * connect.logger({ immediate: true, format: 'dev' }) + * connect.logger(':method :url - :referrer') + * connect.logger(':req[content-type] -> :res[content-type]') + * connect.logger(function(tokens, req, res){ return 'some format string' }) + * + * Defining Tokens: + * + * To define a token, simply invoke `connect.logger.token()` with the + * name and a callback function. The value returned is then available + * as ":type" in this case. + * + * connect.logger.token('type', function(req, res){ return req.headers['content-type']; }) + * + * Defining Formats: + * + * All default formats are defined this way, however it's public API as well: + * + * connect.logger.format('name', 'string or function') + */ + export function logger(options: string): Handler; + + export function logger(options: Function): Handler; + + export function logger(options?: any): Handler; + + /** + * Compile `fmt` into a function. + * + * @param fmt + */ + export function compile(fmt: string): Handler; + + /** + * Define a token function with the given `name`, + * and callback `fn(req, res)`. + * + * @param name + * @param fn + */ + export function token(name: string, fn: Function): any; + + /** + * Define a `fmt` with the given `name`. + */ + export function format(name: string, str: string): any; + + export function format(name: string, str: Function): any; + + /** + * Query: + * + * Automatically parse the query-string when available, + * populating the `req.query` object. + * + * Examples: + * + * connect() + * .use(connect.query()) + * .use(function(req, res){ + * res.end(JSON.stringify(req.query)); + * }); + * + * The `options` passed are provided to qs.parse function. + */ + export function query(options: any): Handler; + + /** + * Reponse time: + * + * Adds the `X-Response-Time` header displaying the response + * duration in milliseconds. + */ + export function responseTime(): Handler; + + /** + * Static cache: + * + * Enables a memory cache layer on top of + * the `static()` middleware, serving popular + * static files. + * + * By default a maximum of 128 objects are + * held in cache, with a max of 256k each, + * totalling ~32mb. + * + * A Least-Recently-Used (LRU) cache algo + * is implemented through the `Cache` object, + * simply rotating cache objects as they are + * hit. This means that increasingly popular + * objects maintain their positions while + * others get shoved out of the stack and + * garbage collected. + * + * Benchmarks: + * + * static(): 2700 rps + * node-static: 5300 rps + * static() + staticCache(): 7500 rps + * + * Options: + * + * - `maxObjects` max cache objects [128] + * - `maxLength` max cache object length 256kb + */ + export function staticCache(options: any): Handler; + + /** + * Timeout: + * + * Times out the request in `ms`, defaulting to `5000`. The + * method `req.clearTimeout()` is added to revert this behaviour + * programmatically within your application's middleware, routes, etc. + * + * The timeout error is passed to `next()` so that you may customize + * the response behaviour. This error has the `.timeout` property as + * well as `.status == 408`. + */ + export function timeout(ms: number): Handler; + + /** + * Vhost: + * + * Setup vhost for the given `hostname` and `server`. + * + * connect() + * .use(connect.vhost('foo.com', fooApp)) + * .use(connect.vhost('bar.com', barApp)) + * .use(connect.vhost('*.com', mainApp)) + * + * The `server` may be a Connect server or + * a regular Node `http.Server`. + * + * @param hostname + * @param server + */ + export function vhost(hostname: string, server: any): Handler; + + export function urlencoded(): any; + + export function multipart(): any; +} \ No newline at end of file diff --git a/jquery.bbq/jquery.bbq-tests.ts b/jquery.bbq/jquery.bbq-tests.ts new file mode 100644 index 000000000..1543ae63c --- /dev/null +++ b/jquery.bbq/jquery.bbq-tests.ts @@ -0,0 +1,1282 @@ +/// +/// + + +// ************** Tests to jquery JQueryParam interface +var myObject = { + a: { + one: 1, + two: 2, + three: 3 + }, + b: [1,2,3] +}; +var recursiveEncoded = $.param(myObject); +var recursiveDecoded = decodeURIComponent($.param(myObject)); +var shallowEncoded = $.param(myObject, true); +var shallowDecoded = decodeURIComponent(shallowEncoded); + +var params = { width:1680, height:1050 }; +var str = jQuery.param(params); +$("#results").text(str); + +// <=1.3.2: +$.param({ a: [2,3,4] }) // "a=2&a=3&a=4" +// >=1.4: +$.param({ a: [2,3,4] }) // "a[]=2&a[]=3&a[]=4" + +// <=1.3.2: +$.param({ a: { b:1,c:2 }, d: [3,4,{ e:5 }] }) // "a=[object+Object]&d=3&d=4&d=[object+Object]" +// >=1.4: +$.param({ a: { b:1,c:2 }, d: [3,4,{ e:5 }] }) // "a[b]=1&a[c]=2&d[]=3&d[]=4&d[2][e]=5" +// ************************************************************* + +// Not sure why this isn't set by default in qunit.js.. +QUnit.jsDump.HTML = false; + +$(function(){ // START CLOSURE + + +var old_jquery = $.fn.jquery < '1.4', + is_chrome = /chrome/i.test( navigator.userAgent ), + params_init = 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&c=1', + init_url, + ajaxcrawlable_init = $.param.fragment.ajaxCrawlable(), + aps = Array.prototype.slice; + +if ( $.param.querystring() !== params_init || $.param.fragment() !== params_init ) { + init_url = window.location.href; + init_url = $.param.querystring( init_url, params_init, 2 ); + init_url = $.param.fragment( init_url, params_init, 2 ); + window.location.href = init_url; +} + +$('#jq_version').html( $.fn.jquery ); + +function notice( txt? ) { + if ( txt ) { + $('#notice').html( txt ); + } else { + $('#notice').hide(); + } +}; + +function run_many_tests(...args: any[]) { + var tests = aps.call( arguments ), + delay = typeof tests[0] === 'number' && tests.shift(), + func_each = $.isFunction( tests[0] ) && tests.shift(), + func_done = $.isFunction( tests[0] ) && tests.shift(), + result; + + function set_result( i, test ) { + result = $.isArray( test ) + ? func_each.apply( this, test ) + : $.isFunction( test ) + ? test( result ) + : ''; + }; + + if ( delay ) { + stop(); + + (function loopy(){ + //test && test.func && test.func( result ); + if ( tests.length ) { + set_result( 0, tests.shift() ); + setTimeout( loopy, delay ); + } else { + func_done && func_done(); + start(); + } + })(); + + } else { + $.each( tests, set_result ); + func_done && func_done(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +QUnit.module( 'jQuery.param' ); + +var params_obj = { a:['4','5','6'], b:{x:['7'], y:'8', z:['9','0','true','false','undefined','']}, c:'1' }, + params_obj_coerce = { a:[4,5,6] },//, b:{x:[7], y:8, z:[9,0,true,false,undefined,'']}, c:1 }, + params_str = params_init, + params_str_old = 'a=4&a=5&a=6&b=[object+Object]&c=1', + + // If a params fragment starts with ! and BBQ is not in ajaxCrawlable mode, + // things can get very ugly, very quickly. + params_obj_bang = { "!a":['4'], a:['5','6'], b:{x:['7'], y:'8', z:['9','0','true','false','undefined','']}, c:'1' }, + params_obj_bang_coerce = { "!a":[4], a:[5,6] };//, b:{x:[7], y:8, z:[9,0,true,false,undefined,'']}, c:1 }; + +test( 'jQuery.param.sorted', function() { + var tests = [ + { + obj: {z:1,b:2,ab:3,bc:4,ba:5,aa:6,a1:7,x:8}, + traditional: false, + expected: 'a1=7&aa=6&ab=3&b=2&ba=5&bc=4&x=8&z=1' + }, + { + obj: {z:1,b:[6,5,4],x:2,a:[3,2,1]}, + traditional: false, + expected: 'a[]=3&a[]=2&a[]=1&b[]=6&b[]=5&b[]=4&x=2&z=1', + expected_old: 'a=3&a=2&a=1&b=6&b=5&b=4&x=2&z=1' + }, + { + obj: {z:1,b:[6,5,4],x:2,a:[3,2,1]}, + traditional: true, + expected: 'a=3&a=2&a=1&b=6&b=5&b=4&x=2&z=1' + }, + { + obj: {a:[[4,[5,6]],[[7,8],9]]}, + traditional: false, + expected: 'a[0][]=4&a[0][1][]=5&a[0][1][]=6&a[1][0][]=7&a[1][0][]=8&a[1][]=9', + expected_old: 'a=4,5,6&a=7,8,9' // obviously not great, but that's the way jQuery used to roll + } + ]; + + if ( $.fn.jquery != '1.4.1' ) { + // this explodes in jQuery 1.4.1 + tests.push({ + obj: {z:1,'b[]':[6,5,4],x:2,'a[]':[3,2,1]}, + obj_alt: {z:1,b:[6,5,4],x:2,a:[3,2,1]}, + traditional: false, + expected: 'a[]=3&a[]=2&a[]=1&b[]=6&b[]=5&b[]=4&x=2&z=1' + }); + } + + expect( tests.length * 2 + 6 ); + + $.each( tests, function(i,test){ + var unsorted = $.param( test.obj, test.traditional ), + sorted = $.param.sorted( test.obj, test.traditional ); + + equal( decodeURIComponent( sorted ), old_jquery && test.expected_old || test.expected, 'params should be sorted' ); + deepEqual( $.deparam( unsorted, true ), $.deparam( sorted, true ), 'sorted params should deparam the same as unsorted params' ) + }); + + equal( $.param.fragment( 'foo', '#b=2&a=1' ), 'foo#a=1&b=2', 'params should be sorted' ); + equal( $.param.fragment( 'foo', '#b=2&a=1', 1 ), 'foo#a=1&b=2', 'params should be sorted' ); + equal( $.param.fragment( 'foo', '#b=2&a=1', 2 ), 'foo#b=2&a=1', 'params should NOT be sorted' ); + equal( $.param.fragment( 'foo#c=3&a=4', '#b=2&a=1' ), 'foo#a=1&b=2&c=3', 'params should be sorted' ); + equal( $.param.fragment( 'foo#c=3&a=4', '#b=2&a=1', 1 ), 'foo#a=4&b=2&c=3', 'params should be sorted' ); + equal( $.param.fragment( 'foo#c=3&a=4', '#b=2&a=1', 2 ), 'foo#b=2&a=1', 'params should NOT be sorted' ); + +}); + +test( 'jQuery.param.querystring', function() { + expect( 11 ); + + equal( $.param.querystring( 'http://example.com/' ), '', 'properly identifying params' ); + equal( $.param.querystring( 'http://example.com/?foo' ),'foo', 'properly identifying params' ); + equal( $.param.querystring( 'http://example.com/?foo#bar' ),'foo', 'properly identifying params' ); + equal( $.param.querystring( 'http://example.com/?foo#bar?baz' ),'foo', 'properly identifying params' ); + equal( $.param.querystring( 'http://example.com/#foo' ),'', 'properly identifying params' ); + equal( $.param.querystring( 'http://example.com/#foo?bar' ),'', 'properly identifying params' ); + + equal( $.param.querystring(), params_str, 'params string from window.location' ); + equal( $.param.querystring( '?' + params_str ), params_str, 'params string from url' ); + equal( $.param.querystring( 'foo.html?' + params_str ), params_str, 'params string from url' ); + equal( $.param.querystring( 'http://a:b@example.com:1234/foo.html?' + params_str ), params_str, 'params string from url' ); + equal( $.param.querystring( 'http://a:b@example.com:1234/foo.html?' + params_str + '#bippity-boppity-boo' ), params_str, 'params string from url' ); +}); + +test( 'jQuery.param.querystring - build URL', function() { + expect( 10 ); + + function fake_encode( params_str ) { + return '?' + $.map( params_str.split('&'), encodeURIComponent ).join('&').replace( /%3D/g, '=' ).replace( /%2B/g, '+' ); + } + + var pre = 'http://a:b@example.com:1234/foo.html', + post = '#get-on-the-floor', + current_url = pre + post; + + run_many_tests( + + // execute this for each array item + function(){ + current_url = $.param.querystring.apply( this, [ current_url ].concat( aps.call( arguments ) ) ); + }, + + // tests: + + [ { a:'2' } ], + + function(result){ + equal( current_url, pre + '?a=2' + post, '$.param.querystring( url, Object )' ); + }, + + [ { b:'2' } ], + + function(result){ + equal( current_url, pre + '?a=2&b=2' + post, '$.param.querystring( url, Object )' ); + }, + + [ { c:true, d:false, e:'undefined', f:'' } ], + + function(result){ + equal( current_url, pre + '?a=2&b=2&c=true&d=false&e=undefined&f=' + post, '$.param.querystring( url, Object )' ); + }, + + [ { a:[4,5,6]}],//, b:{x:[7], y:8, z:[9,0,'true','false','undefined','']} }, 2 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]='; + + equal( current_url, pre + fake_encode( params ) + post, '$.param.querystring( url, Object, 2 )' ); + }, + + [ { a:'1', c:'2' }, 1 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&c=2' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&c=2'; + + equal( current_url, pre + fake_encode( params ) + post, '$.param.querystring( url, Object, 1 )' ); + }, + + [ 'foo=1' ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&c=2&foo=1' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&c=2&foo=1'; + + equal( current_url, pre + fake_encode( params ) + post, '$.param.querystring( url, String )' ); + }, + + [ 'foo=2&bar=3', 1 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&bar=3&c=2&foo=1' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&bar=3&c=2&foo=1'; + + equal( current_url, pre + fake_encode( params ) + post, '$.param.querystring( url, String, 1 )' ); + }, + + [ 'http://example.com/test.html?/path/to/file.php#the-cow-goes-moo', 2 ], + + function(result){ + equal( current_url, pre + '?/path/to/file.php' + post, '$.param.querystring( url, String, 2 )' ); + }, + + [ '?another-example', 2 ], + + function(result){ + equal( current_url, pre + '?another-example' + post, '$.param.querystring( url, String, 2 )' ); + }, + + [ 'i_am_out_of_witty_strings', 2 ], + + function(result){ + equal( current_url, pre + '?i_am_out_of_witty_strings' + post, '$.param.querystring( url, String, 2 )' ); + } + + ); + +}); + +test( 'jQuery.param.fragment', function() { + expect( 29 ); + + equal( $.param.fragment( 'http://example.com/' ), '', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo' ),'', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo#bar' ),'bar', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo#bar?baz' ),'bar?baz', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/#foo' ),'foo', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/#foo?bar' ),'foo?bar', 'properly identifying params' ); + + equal( $.param.fragment( 'http://example.com/' ), '', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo' ),'', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo#!bar' ),'!bar', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo#!bar?baz' ),'!bar?baz', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/#!foo' ),'!foo', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/#!foo?bar' ),'!foo?bar', 'properly identifying params' ); + + equal( $.param.fragment(), params_str, 'params string from window.location' ); + equal( $.param.fragment( '#' + params_str ), params_str, 'params string from url' ); + equal( $.param.fragment( 'foo.html#' + params_str ), params_str, 'params string from url' ); + equal( $.param.fragment( 'http://a:b@example.com:1234/foo.html#' + params_str ), params_str, 'params string from url' ); + equal( $.param.fragment( 'http://a:b@example.com:1234/foo.html?bippity-boppity-boo#' + params_str ), params_str, 'params string from url' ); + + $.param.fragment.ajaxCrawlable( true ); + + equal( $.param.fragment( 'http://example.com/' ), '', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo' ),'', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo#bar' ),'bar', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo#bar?baz' ),'bar?baz', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/#foo' ),'foo', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/#foo?bar' ),'foo?bar', 'properly identifying params' ); + + equal( $.param.fragment( 'http://example.com/' ), '', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo' ),'', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo#!bar' ),'bar', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/?foo#!bar?baz' ),'bar?baz', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/#!foo' ),'foo', 'properly identifying params' ); + equal( $.param.fragment( 'http://example.com/#!foo?bar' ),'foo?bar', 'properly identifying params' ); + + $.param.fragment.ajaxCrawlable( false ); + +}); + +test( 'jQuery.param.fragment - build URL', function() { + expect( 40 ); + + function fake_encode( params_str ) { + return '#' + $.map( params_str.split('&'), encodeURIComponent ).join('&').replace( /%3D/g, '=' ).replace( /%2B/g, '+' ); + } + + var pre = 'http://a:b@example.com:1234/foo.html?and-dance-with-me', + current_url = pre; + + run_many_tests( + + // execute this for each array item + function(){ + current_url = $.param.fragment.apply( this, [ current_url ].concat( aps.call( arguments ) ) ); + }, + + // tests: + + [ { a:'2' } ], + + function(result){ + equal( current_url, pre + '#a=2', '$.param.fragment( url, Object )' ); + }, + + [ { b:'2' } ], + + function(result){ + equal( current_url, pre + '#a=2&b=2', '$.param.fragment( url, Object )' ); + }, + + [ { c:true, d:false, e:'undefined', f:'' } ], + + function(result){ + equal( current_url, pre + '#a=2&b=2&c=true&d=false&e=undefined&f=', '$.param.fragment( url, Object )' ); + }, + + [ { a:[4,5,6]}],//, b:{x:[7], y:8, z:[9,0,'true','false','undefined','']} }, 2 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]='; + + equal( current_url, pre + fake_encode( params ), '$.param.fragment( url, Object, 2 )' ); + }, + + [ { a:'1', c:'2' }, 1 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&c=2' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&c=2'; + + equal( current_url, pre + fake_encode( params ), '$.param.fragment( url, Object, 1 )' ); + }, + + [ 'foo=1' ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&c=2&foo=1' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&c=2&foo=1'; + + equal( current_url, pre + fake_encode( params ), '$.param.fragment( url, String )' ); + }, + + [ 'foo=2&bar=3', 1 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&bar=3&c=2&foo=1' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&bar=3&c=2&foo=1'; + + equal( current_url, pre + fake_encode( params ), '$.param.fragment( url, String, 1 )' ); + }, + + [ 'http://example.com/test.html?the-cow-goes-moo#/path/to/file.php', 2 ], + + function(result){ + equal( current_url, pre + '#/path/to/file.php', '$.param.fragment( url, String, 2 )' ); + }, + + [ '#another-example', 2 ], + + function(result){ + equal( current_url, pre + '#another-example', '$.param.fragment( url, String, 2 )' ); + }, + + [ 'i_am_out_of_witty_strings', 2 ], + + function(result){ + equal( current_url, pre + '#i_am_out_of_witty_strings', '$.param.fragment( url, String, 2 )' ); + } + + ); + + $.param.fragment.ajaxCrawlable( true ); + + equal( $.param.fragment( 'foo', {} ) , 'foo#!', '$.param.fragment( url, Object )' ); + equal( $.param.fragment( 'foo', { b:2, a:1 } ) , 'foo#!a=1&b=2', '$.param.fragment( url, Object )' ); + equal( $.param.fragment( 'foo#', { b:2, a:1 } ) , 'foo#!a=1&b=2', '$.param.fragment( url, Object )' ); + equal( $.param.fragment( 'foo#!', { b:2, a:1 } ) , 'foo#!a=1&b=2', '$.param.fragment( url, Object )' ); + equal( $.param.fragment( 'foo#c=3&a=4', { b:2, a:1 } ) , 'foo#!a=1&b=2&c=3', '$.param.fragment( url, Object )' ); + equal( $.param.fragment( 'foo#!c=3&a=4', { b:2, a:1 } ) , 'foo#!a=1&b=2&c=3', '$.param.fragment( url, Object )' ); + + equal( $.param.fragment( 'foo', '' ) , 'foo#!', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo', 'b=2&a=1' ) , 'foo#!a=1&b=2', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#', 'b=2&a=1' ) , 'foo#!a=1&b=2', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#!', 'b=2&a=1' ) , 'foo#!a=1&b=2', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#c=3&a=4', 'b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#!c=3&a=4', 'b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.param.fragment( url, String )' ); + + equal( $.param.fragment( 'foo', '#' ) , 'foo#!', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo', '#b=2&a=1' ) , 'foo#!a=1&b=2', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#', '#b=2&a=1' ) , 'foo#!a=1&b=2', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#!', '#b=2&a=1' ) , 'foo#!a=1&b=2', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#c=3&a=4', '#b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#!c=3&a=4', '#b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.param.fragment( url, String )' ); + + equal( $.param.fragment( 'foo', '#!' ) , 'foo#!', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo', '#!b=2&a=1' ) , 'foo#!a=1&b=2', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#', '#!b=2&a=1' ) , 'foo#!a=1&b=2', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#!', '#!b=2&a=1' ) , 'foo#!a=1&b=2', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#c=3&a=4', '#!b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#!c=3&a=4', '#!b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.param.fragment( url, String )' ); + + $.param.fragment.ajaxCrawlable( false ); + + // If a params fragment starts with ! and BBQ is not in ajaxCrawlable mode, + // things can get very ugly, very quickly. + equal( $.param.fragment( 'foo', '#!' ) , 'foo#!=', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo', '#!b=2&a=1' ) , 'foo#!b=2&a=1', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#', '#!b=2&a=1' ) , 'foo#!b=2&a=1', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#!', '#!b=2&a=1' ) , 'foo#!=&!b=2&a=1', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#c=3&a=4', '#!b=2&a=1' ) , 'foo#!b=2&a=1&c=3', '$.param.fragment( url, String )' ); + equal( $.param.fragment( 'foo#!c=3&a=4', '#!b=2&a=1' ) , 'foo#!b=2&!c=3&a=1', '$.param.fragment( url, String )' ); + +}); + +test( 'jQuery.param.fragment.ajaxCrawlable', function() { + expect( 5 ); + + equal( ajaxcrawlable_init, false, 'ajaxCrawlable is disabled by default' ); + equal( $.param.fragment.ajaxCrawlable( true ), true, 'enabling ajaxCrawlable should return true' ); + equal( $.param.fragment.ajaxCrawlable(), true, 'ajaxCrawlable is now enabled' ); + equal( $.param.fragment.ajaxCrawlable( false ), false, 'disabling ajaxCrawlable should return false' ); + equal( $.param.fragment.ajaxCrawlable(), false, 'ajaxCrawlable is now disabled' ); +}); + +test( 'jQuery.param.fragment.noEscape', function() { + expect( 2 ); + + equal( $.param.fragment( '#', { foo: '/a,b@c$d+e&f=g h!' } ), '#foo=/a,b%40c%24d%2Be%26f%3Dg+h!', '/, should be unescaped, everything else but space (+) should be urlencoded' ); + + $.param.fragment.ajaxCrawlable( true ); + + equal( $.param.fragment( '#', { foo: '/a,b@c$d+e&f=g h!' } ), '#!foo=/a,b%40c%24d%2Be%26f%3Dg+h!', '/, should be unescaped, everything else but ! and space (+) should be urlencoded' ); + + $.param.fragment.ajaxCrawlable( false ); +}); + + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +QUnit.module( 'jQuery.deparam' ); + +test( 'jQuery.deparam - 1.4-style params', function() { + expect( 2 ); + deepEqual( $.deparam( params_str ), params_obj, '$.deparam( String )' ); + deepEqual( $.deparam( params_str, true ), params_obj_coerce, '$.deparam( String, true )' ); +}); + +test( 'jQuery.deparam - pre-1.4-style params', function() { + var params_str = 'a=1&a=2&a=3&b=4&c=5&c=6&c=true&c=false&c=undefined&c=&d=7', + params_obj = { a:['1','2','3'], b:'4', c:['5','6','true','false','undefined',''], d:'7' }, + params_obj_coerce = { a:[1,2,3], b:4, c:[5,6,true,false,undefined,''], d:7 }; + + expect( 2 ); + deepEqual( $.deparam( params_str ), params_obj, '$.deparam( String )' ); + deepEqual( $.deparam( params_str, true ), params_obj_coerce, '$.deparam( String, true )' ); +}); + +test( 'jQuery.deparam.querystring', function() { + expect( 12 ); + + deepEqual( $.deparam.querystring(), params_obj, 'params obj from window.location' ); + deepEqual( $.deparam.querystring( /*true*/ ), params_obj_coerce, 'params obj from window.location, coerced' ); + deepEqual( $.deparam.querystring( params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.querystring( params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.querystring( '?' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.querystring( '?' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.querystring( 'foo.html?' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.querystring( 'foo.html?' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.querystring( 'http://a:b@example.com:1234/foo.html?' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.querystring( 'http://a:b@example.com:1234/foo.html?' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.querystring( 'http://a:b@example.com:1234/foo.html?' + params_str + '#bippity-boppity-boo' ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.querystring( 'http://a:b@example.com:1234/foo.html?' + params_str + '#bippity-boppity-boo', true ), params_obj_coerce, 'params obj from string, coerced' ); +}); + +test( 'jQuery.deparam.fragment', function() { + expect( 36 ); + + deepEqual( $.deparam.fragment(), params_obj, 'params obj from window.location' ); + deepEqual( $.deparam.fragment( /*true*/ ), params_obj_coerce, 'params obj from window.location, coerced' ); + deepEqual( $.deparam.fragment( params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + + deepEqual( $.deparam.fragment( '#' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( '#' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'foo.html#' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'foo.html#' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html#' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html#' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html?bippity-boppity-boo#' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html?bippity-boppity-boo#' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + + // If a params fragment starts with ! and BBQ is not in ajaxCrawlable mode, + // things can get very ugly, very quickly. + deepEqual( $.deparam.fragment( '#!' + params_str ), params_obj_bang, 'params obj from string' ); + deepEqual( $.deparam.fragment( '#!' + params_str, true ), params_obj_bang_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'foo.html#!' + params_str ), params_obj_bang, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'foo.html#!' + params_str, true ), params_obj_bang_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html#!' + params_str ), params_obj_bang, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html#!' + params_str, true ), params_obj_bang_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html?bippity-boppity-boo#!' + params_str ), params_obj_bang, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html?bippity-boppity-boo#!' + params_str, true ), params_obj_bang_coerce, 'params obj from string, coerced' ); + + $.param.fragment.ajaxCrawlable( true ); + + deepEqual( $.deparam.fragment( '#' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( '#' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'foo.html#' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'foo.html#' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html#' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html#' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html?bippity-boppity-boo#' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html?bippity-boppity-boo#' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + + deepEqual( $.deparam.fragment( '#!' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( '#!' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'foo.html#!' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'foo.html#!' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html#!' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html#!' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html?bippity-boppity-boo#!' + params_str ), params_obj, 'params obj from string' ); + deepEqual( $.deparam.fragment( 'http://a:b@example.com:1234/foo.html?bippity-boppity-boo#!' + params_str, true ), params_obj_coerce, 'params obj from string, coerced' ); + + $.param.fragment.ajaxCrawlable( false ); +}); + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +QUnit.module( 'jQuery.fn' ); + +$.elemUrlAttr({ span: 'arbitrary_attr' }); +var test_elems = 'a form link span'.split(' '); + +function init_url_attr( container, url ) { + var container = $('
').hide().appendTo('body'); + $.each( test_elems, function(i,v){ + $('<' + v + '/>') + .attr( $.elemUrlAttr()[ v ], url ) + .appendTo( container ); + }); + return container; +}; + +function test_url_attr( container ) { + var url; + + $.each( test_elems, function(i,v){ + var val = container.children( v ).attr( $.elemUrlAttr()[ v ] ); + if ( !url ) { + url = val; + } else if ( val !== url ) { + url = -1; + } + }); + + return url; +}; + +test( 'jQuery.fn.querystring', function() { + expect( 60 ); + + function fake_encode( params_str ) { + return '?' + $.map( params_str.split('&'), encodeURIComponent ).join('&').replace( /%3D/g, '=' ).replace( /%2B/g, '+' ); + } + + var pre = 'http://a:b@example.com:1234/foo.html', + post = '#get-on-the-floor', + current_url = pre + post; + + run_many_tests( + + // execute this for each array item + function(){ + var container, + elems; + + container = init_url_attr( container, current_url ); + elems = container.children('span'); + equal( elems.length, 1, 'select the correct elements' ); + equal( elems.querystring.apply( elems, [ 'arbitrary_attr' ].concat( aps.call( arguments ) ) ), elems, 'pass query string' ); + + container = init_url_attr( container, current_url ); + elems = container.children('a, link'); + equal( elems.length, 2, 'select the correct elements' ); + equal( elems.querystring.apply( elems, [ 'href' ].concat( aps.call( arguments ) ) ), elems, 'pass query string' ); + + container = init_url_attr( container, current_url ); + elems = container.children(); + equal( elems.querystring.apply( elems, aps.call( arguments ) ), elems, 'pass query string' ); + + current_url = test_url_attr( container ); + }, + + // tests: + + [ { a:'2' } ], + + function(result){ + equal( current_url, pre + '?a=2' + post, '$.fn.querystring( url, Object )' ); + }, + + [ { b:'2' } ], + + function(result){ + equal( current_url, pre + '?a=2&b=2' + post, '$.fn.querystring( url, Object )' ); + }, + + [ { c:true, d:false, e:'undefined', f:'' } ], + + function(result){ + equal( current_url, pre + '?a=2&b=2&c=true&d=false&e=undefined&f=' + post, '$.fn.querystring( url, Object )' ); + }, + + [ { a:[4,5,6]}],//, b:{x:[7], y:8, z:[9,0,'true','false','undefined','']} }, 2 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]='; + + equal( current_url, pre + fake_encode( params ) + post, '$.fn.querystring( url, Object, 2 )' ); + }, + + [ { a:'1', c:'2' }, 1 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&c=2' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&c=2'; + + equal( current_url, pre + fake_encode( params ) + post, '$.fn.querystring( url, Object, 1 )' ); + }, + + [ 'foo=1' ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&c=2&foo=1' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&c=2&foo=1'; + + equal( current_url, pre + fake_encode( params ) + post, '$.fn.querystring( url, String )' ); + }, + + [ 'foo=2&bar=3', 1 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&bar=3&c=2&foo=1' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&bar=3&c=2&foo=1'; + + equal( current_url, pre + fake_encode( params ) + post, '$.fn.querystring( url, String, 1 )' ); + }, + + [ 'http://example.com/test.html?/path/to/file.php#the-cow-goes-moo', 2 ], + + function(result){ + equal( current_url, pre + '?/path/to/file.php' + post, '$.fn.querystring( url, String, 2 )' ); + }, + + [ '?another-example', 2 ], + + function(result){ + equal( current_url, pre + '?another-example' + post, '$.fn.querystring( url, String, 2 )' ); + }, + + [ 'i_am_out_of_witty_strings', 2 ], + + function(result){ + equal( current_url, pre + '?i_am_out_of_witty_strings' + post, '$.fn.querystring( url, String, 2 )' ); + } + + ); + +}); + +test( 'jQuery.fn.fragment', function() { + expect( 240 ); + + function fake_encode( params_str ) { + return '#' + $.map( params_str.split('&'), encodeURIComponent ).join('&').replace( /%3D/g, '=' ).replace( /%2B/g, '+' ); + } + + var pre = 'http://a:b@example.com:1234/foo.html?and-dance-with-me', + current_url = pre; + + run_many_tests( + + // execute this for each array item + function( params, merge_mode ){ + current_url = test_fn_fragment( current_url, params, merge_mode ); + }, + + // tests: + + [ { a:'2' } ], + + function(result){ + equal( current_url, pre + '#a=2', '$.fn.fragment( url, Object )' ); + }, + + [ { b:'2' } ], + + function(result){ + equal( current_url, pre + '#a=2&b=2', '$.fn.fragment( url, Object )' ); + }, + + [ { c:true, d:false, e:'undefined', f:'' } ], + + function(result){ + equal( current_url, pre + '#a=2&b=2&c=true&d=false&e=undefined&f=', '$.fn.fragment( url, Object )' ); + }, + + [ { a:[4,5,6]}],//, b:{x:[7], y:8, z:[9,0,'true','false','undefined','']} }, 2 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]='; + + equal( current_url, pre + fake_encode( params ), '$.fn.fragment( url, Object, 2 )' ); + }, + + [ { a:'1', c:'2' }, 1 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&c=2' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&c=2'; + + equal( current_url, pre + fake_encode( params ), '$.fn.fragment( url, Object, 1 )' ); + }, + + [ 'foo=1' ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&c=2&foo=1' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&c=2&foo=1'; + + equal( current_url, pre + fake_encode( params ), '$.fn.fragment( url, String )' ); + }, + + [ 'foo=2&bar=3', 1 ], + + function(result){ + var params = old_jquery + ? 'a=4&a=5&a=6&b=[object+Object]&bar=3&c=2&foo=1' + : 'a[]=4&a[]=5&a[]=6&b[x][]=7&b[y]=8&b[z][]=9&b[z][]=0&b[z][]=true&b[z][]=false&b[z][]=undefined&b[z][]=&bar=3&c=2&foo=1'; + + equal( current_url, pre + fake_encode( params ), '$.fn.fragment( url, String, 1 )' ); + }, + + [ 'http://example.com/test.html?the-cow-goes-moo#/path/to/file.php', 2 ], + + function(result){ + equal( current_url, pre + '#/path/to/file.php', '$.fn.fragment( url, String, 2 )' ); + }, + + [ '#another-example', 2 ], + + function(result){ + equal( current_url, pre + '#another-example', '$.fn.fragment( url, String, 2 )' ); + }, + + [ 'i_am_out_of_witty_strings', 2 ], + + function(result){ + equal( current_url, pre + '#i_am_out_of_witty_strings', '$.fn.fragment( url, String, 2 )' ); + } + + ); + + $.param.fragment.ajaxCrawlable( true ); + + function test_fn_fragment( url, params, merge_mode? ) { + var container, + elems; + + container = init_url_attr( container, url ); + elems = container.children('span'); + equal( elems.length, 1, 'select the correct elements' ); + equal( elems.fragment( 'arbitrary_attr', params, merge_mode ), elems, 'pass fragment' ); + + container = init_url_attr( container, url ); + elems = container.children('a, link'); + equal( elems.length, 2, 'select the correct elements' ); + equal( elems.fragment( params, merge_mode ), elems, 'pass fragment' ); + + container = init_url_attr( container, url ); + elems = container.children(); + equal( elems.fragment( params, merge_mode ), elems, 'pass fragment' ); + + return test_url_attr( container ); + }; + + equal( test_fn_fragment( 'foo', {} ) , 'foo#!', '$.fn.fragment( url, Object )' ); + equal( test_fn_fragment( 'foo', { b:2, a:1 } ) , 'foo#!a=1&b=2', '$.fn.fragment( url, Object )' ); + equal( test_fn_fragment( 'foo#', { b:2, a:1 } ) , 'foo#!a=1&b=2', '$.fn.fragment( url, Object )' ); + equal( test_fn_fragment( 'foo#!', { b:2, a:1 } ) , 'foo#!a=1&b=2', '$.fn.fragment( url, Object )' ); + equal( test_fn_fragment( 'foo#c=3&a=4', { b:2, a:1 } ) , 'foo#!a=1&b=2&c=3', '$.fn.fragment( url, Object )' ); + equal( test_fn_fragment( 'foo#!c=3&a=4', { b:2, a:1 } ) , 'foo#!a=1&b=2&c=3', '$.fn.fragment( url, Object )' ); + + equal( test_fn_fragment( 'foo', '' ) , 'foo#!', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo', 'b=2&a=1' ) , 'foo#!a=1&b=2', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#', 'b=2&a=1' ) , 'foo#!a=1&b=2', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#!', 'b=2&a=1' ) , 'foo#!a=1&b=2', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#c=3&a=4', 'b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#!c=3&a=4', 'b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.fn.fragment( url, String )' ); + + equal( test_fn_fragment( 'foo', '#' ) , 'foo#!', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo', '#b=2&a=1' ) , 'foo#!a=1&b=2', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#', '#b=2&a=1' ) , 'foo#!a=1&b=2', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#!', '#b=2&a=1' ) , 'foo#!a=1&b=2', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#c=3&a=4', '#b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#!c=3&a=4', '#b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.fn.fragment( url, String )' ); + + equal( test_fn_fragment( 'foo', '#!' ) , 'foo#!', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo', '#!b=2&a=1' ) , 'foo#!a=1&b=2', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#', '#!b=2&a=1' ) , 'foo#!a=1&b=2', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#!', '#!b=2&a=1' ) , 'foo#!a=1&b=2', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#c=3&a=4', '#!b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#!c=3&a=4', '#!b=2&a=1' ) , 'foo#!a=1&b=2&c=3', '$.fn.fragment( url, String )' ); + + $.param.fragment.ajaxCrawlable( false ); + + // If a params fragment starts with ! and BBQ is not in ajaxCrawlable mode, + // things can get very ugly, very quickly. + equal( test_fn_fragment( 'foo', '#!' ) , 'foo#!=', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo', '#!b=2&a=1' ) , 'foo#!b=2&a=1', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#', '#!b=2&a=1' ) , 'foo#!b=2&a=1', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#!', '#!b=2&a=1' ) , 'foo#!=&!b=2&a=1', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#c=3&a=4', '#!b=2&a=1' ) , 'foo#!b=2&a=1&c=3', '$.fn.fragment( url, String )' ); + equal( test_fn_fragment( 'foo#!c=3&a=4', '#!b=2&a=1' ) , 'foo#!b=2&!c=3&a=1', '$.fn.fragment( url, String )' ); + +}); + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +QUnit.module( 'jQuery.bbq' ); + +test( 'jQuery.bbq.pushState(), jQuery.bbq.getState(), jQuery.bbq.removeState(), window.onhashchange', function() { + expect( old_jquery ? 95 : 167 ); + + var a, b, c, d, e, f, x, y, hash, hash_actual, obj, event, msg = 'Testing window.onhashchange and history'; + + $.bbq.pushState(); + equal( window.location.hash.replace( /^#/, ''), '', 'window.location hash should be empty' ); + + $.bbq.pushState({ a:'1', b:'1' }); + deepEqual( $.deparam.fragment(), { a:'1', b:'1' }, 'hash should be set properly' ); + + $(window).bind( 'hashchange', function(evt) { + var hash_str = $.param.fragment(), + param_obj = $.bbq.getState(), + param_val = $.bbq.getState( 'param_name' ); + + event = evt; + hash = $.param.fragment(); + hash_actual = location.hash; + obj = { str: $.bbq.getState(), coerce: $.bbq.getState( true ) }; + a = { str: $.bbq.getState( 'a' ), coerce: $.bbq.getState( 'a', true ) }; + b = { str: $.bbq.getState( 'b' ), coerce: $.bbq.getState( 'b', true ) }; + c = { str: $.bbq.getState( 'c' ), coerce: $.bbq.getState( 'c', true ) }; + d = { str: $.bbq.getState( 'd' ), coerce: $.bbq.getState( 'd', true ) }; + e = { str: $.bbq.getState( 'e' ), coerce: $.bbq.getState( 'e', true ) }; + f = { str: $.bbq.getState( 'f' ), coerce: $.bbq.getState( 'f', true ) }; + + }).trigger( 'hashchange' ); + + deepEqual( obj.str, { a:'1', b:'1' }, 'hashchange triggered manually: $.bbq.getState()' ); + deepEqual( obj.coerce, { a:1, b:1 }, 'hashchange triggered manually: $.bbq.getState( true )' ); + equal( a.str, '1', 'hashchange triggered manually: $.bbq.getState( "a" )' ); + equal( a.coerce, 1, 'hashchange triggered manually: $.bbq.getState( "a", true )' ); + + if ( !old_jquery ) { + deepEqual( event.getState(), { a:'1', b:'1' }, 'hashchange triggered manually: event.getState()' ); + deepEqual( event.getState(true), { a:1, b:1 }, 'hashchange triggered manually: event.getState( true )' ); + equal( event.getState('a'), '1', 'hashchange triggered manually: event.getState( "a" )' ); + equal( event.getState('a',true), 1, 'hashchange triggered manually: event.getState( "a", true )' ); + } + + run_many_tests( + // run asynchronously + 250, + + // execute this for each array item + function(){ + notice( msg += '.' ); + $.bbq.pushState.apply( this, aps.call( arguments ) ); + }, + + // execute this at the end + function(){ + notice(); + }, + + // tests: + + [ { a:'2' } ], + + function(result){ + equal( hash_actual, '#' + hash, 'hash should begin with #!' ); + deepEqual( obj.str, { a:'2', b:'1' }, '$.bbq.getState()' ); + deepEqual( obj.coerce, { a:2, b:1 }, '$.bbq.getState( true )' ); + equal( a.str, '2', '$.bbq.getState( "a" )' ); + equal( a.coerce, 2, '$.bbq.getState( "a", true )' ); + if ( !old_jquery ) { + deepEqual( event.getState(), { a:'2', b:'1' }, 'event.getState()' ); + deepEqual( event.getState(true), { a:2, b:1 }, 'event.getState( true )' ); + equal( event.getState('a'), '2', 'event.getState( "a" )' ); + equal( event.getState('a',true), 2, 'event.getState( "a", true )' ); + } + }, + + [ { b:'2' } ], + + function(result){ + equal( hash_actual, '#' + hash, 'hash should begin with #!' ); + deepEqual( obj.str, { a:'2', b:'2' }, '$.bbq.getState()' ); + deepEqual( obj.coerce, { a:2, b:2 }, '$.bbq.getState( true )' ); + equal( b.str, '2', '$.bbq.getState( "b" )' ); + equal( b.coerce, 2, '$.bbq.getState( "b", true )' ); + if ( !old_jquery ) { + deepEqual( event.getState(), { a:'2', b:'2' }, 'event.getState()' ); + deepEqual( event.getState(true), { a:2, b:2 }, 'event.getState( true )' ); + equal( event.getState('b'), '2', 'event.getState( "b" )' ); + equal( event.getState('b',true), 2, 'event.getState( "b", true )' ); + } + }, + + [ { c:true, d:false, e:'undefined', f:'' } ], + + function(result){ + equal( hash_actual, '#' + hash, 'hash should begin with #!' ); + deepEqual( obj.str, { a:'2', b:'2', c:'true', d:'false', e:'undefined', f:'' }, '$.bbq.getState()' ); + deepEqual( obj.coerce, { a:2, b:2, c:true, d:false, e:undefined, f:'' }, '$.bbq.getState( true )' ); + equal( c.str, 'true', '$.bbq.getState( "c" )' ); + equal( c.coerce, true, '$.bbq.getState( "c", true )' ); + equal( d.str, 'false', '$.bbq.getState( "d" )' ); + equal( d.coerce, false, '$.bbq.getState( "d", true )' ); + equal( e.str, 'undefined', '$.bbq.getState( "e" )' ); + equal( e.coerce, undefined, '$.bbq.getState( "e", true )' ); + equal( f.str, '', '$.bbq.getState( "f" )' ); + equal( f.coerce, '', '$.bbq.getState( "f", true )' ); + if ( !old_jquery ) { + deepEqual( event.getState(), { a:'2', b:'2', c:'true', d:'false', e:'undefined', f:'' }, 'event.getState()' ); + deepEqual( event.getState(true), { a:2, b:2, c:true, d:false, e:undefined, f:'' }, 'event.getState( true )' ); + equal( event.getState('c'), 'true', 'event.getState( "c" )' ); + equal( event.getState('c',true), true, 'event.getState( "c", true )' ); + equal( event.getState('d'), 'false', 'event.getState( "d" )' ); + equal( event.getState('d',true), false, 'event.getState( "d", true )' ); + equal( event.getState('e'), 'undefined', 'event.getState( "e" )' ); + equal( event.getState('e',true), undefined, 'event.getState( "e", true )' ); + equal( event.getState('f'), '', 'event.getState( "f" )' ); + equal( event.getState('f',true), '', 'event.getState( "f", true )' ); + } + }, + + function(result){ + $.param.fragment.ajaxCrawlable( true ); + }, + + function(result){ + $.bbq.removeState( 'c' ); + }, + + function(result){ + equal( hash_actual, '#!' + hash, 'hash should begin with #!' ); + deepEqual( obj.str, { a:'2', b:'2', d:'false', e:'undefined', f:'' }, '$.bbq.getState()' ); + deepEqual( obj.coerce, { a:2, b:2, d:false, e:undefined, f:'' }, '$.bbq.getState( true )' ); + equal( a.str, '2', '$.bbq.getState( "a" )' ); + equal( a.coerce, 2, '$.bbq.getState( "a", true )' ); + equal( b.str, '2', '$.bbq.getState( "b" )' ); + equal( b.coerce, 2, '$.bbq.getState( "b", true )' ); + equal( c.str, undefined, '$.bbq.getState( "c" )' ); + equal( c.coerce, undefined, '$.bbq.getState( "c", true )' ); + equal( d.str, 'false', '$.bbq.getState( "d" )' ); + equal( d.coerce, false, '$.bbq.getState( "d", true )' ); + equal( e.str, 'undefined', '$.bbq.getState( "e" )' ); + equal( e.coerce, undefined, '$.bbq.getState( "e", true )' ); + equal( f.str, '', '$.bbq.getState( "f" )' ); + equal( f.coerce, '', '$.bbq.getState( "f", true )' ); + if ( !old_jquery ) { + deepEqual( event.getState(), { a:'2', b:'2', d:'false', e:'undefined', f:'' }, 'event.getState()' ); + deepEqual( event.getState(true), { a:2, b:2, d:false, e:undefined, f:'' }, 'event.getState( true )' ); + equal( event.getState('a'), '2', 'event.getState( "a" )' ); + equal( event.getState('a',true), 2, 'event.getState( "a", true )' ); + equal( event.getState('b'), '2', 'event.getState( "b" )' ); + equal( event.getState('b',true), 2, 'event.getState( "b", true )' ); + equal( event.getState('c'), undefined, 'event.getState( "c" )' ); + equal( event.getState('c',true), undefined, 'event.getState( "c", true )' ); + equal( event.getState('d'), 'false', 'event.getState( "d" )' ); + equal( event.getState('d',true), false, 'event.getState( "d", true )' ); + equal( event.getState('e'), 'undefined', 'event.getState( "e" )' ); + equal( event.getState('e',true), undefined, 'event.getState( "e", true )' ); + equal( event.getState('f'), '', 'event.getState( "f" )' ); + equal( event.getState('f',true), '', 'event.getState( "f", true )' ); + } + }, + + function(result){ + $.bbq.removeState( [ 'd', 'e', 'f', 'nonexistent' ] ); + }, + + function(result){ + equal( hash_actual, '#!' + hash, 'hash should begin with #!' ); + deepEqual( obj.str, { a:'2', b:'2' }, '$.bbq.getState()' ); + deepEqual( obj.coerce, { a:2, b:2 }, '$.bbq.getState( true )' ); + equal( a.str, '2', '$.bbq.getState( "a" )' ); + equal( a.coerce, 2, '$.bbq.getState( "a", true )' ); + equal( b.str, '2', '$.bbq.getState( "b" )' ); + equal( b.coerce, 2, '$.bbq.getState( "b", true )' ); + equal( c.str, undefined, '$.bbq.getState( "c" )' ); + equal( c.coerce, undefined, '$.bbq.getState( "c", true )' ); + equal( d.str, undefined, '$.bbq.getState( "d" )' ); + equal( d.coerce, undefined, '$.bbq.getState( "d", true )' ); + equal( e.str, undefined, '$.bbq.getState( "e" )' ); + equal( e.coerce, undefined, '$.bbq.getState( "e", true )' ); + equal( f.str, undefined, '$.bbq.getState( "f" )' ); + equal( f.coerce, undefined, '$.bbq.getState( "f", true )' ); + if ( !old_jquery ) { + deepEqual( event.getState(), { a:'2', b:'2' }, 'event.getState()' ); + deepEqual( event.getState(true), { a:2, b:2 }, 'event.getState( true )' ); + equal( event.getState('a'), '2', 'event.getState( "a" )' ); + equal( event.getState('a',true), 2, 'event.getState( "a", true )' ); + equal( event.getState('b'), '2', 'event.getState( "b" )' ); + equal( event.getState('b',true), 2, 'event.getState( "b", true )' ); + equal( event.getState('c'), undefined, 'event.getState( "c" )' ); + equal( event.getState('c',true), undefined, 'event.getState( "c", true )' ); + equal( event.getState('d'), undefined, 'event.getState( "d" )' ); + equal( event.getState('d',true), undefined, 'event.getState( "d", true )' ); + equal( event.getState('e'), undefined, 'event.getState( "e" )' ); + equal( event.getState('e',true), undefined, 'event.getState( "e", true )' ); + equal( event.getState('f'), undefined, 'event.getState( "f" )' ); + equal( event.getState('f',true), undefined, 'event.getState( "f", true )' ); + } + }, + + function(result){ + $.bbq.removeState(); + }, + + function(result){ + equal( hash_actual, '#!', 'hash should just be #!' ); + deepEqual( obj.str, {}, '$.bbq.getState()' ); + deepEqual( obj.coerce, {}, '$.bbq.getState( true )' ); + equal( a.str, undefined, '$.bbq.getState( "a" )' ); + equal( a.coerce, undefined, '$.bbq.getState( "a", true )' ); + equal( b.str, undefined, '$.bbq.getState( "b" )' ); + equal( b.coerce, undefined, '$.bbq.getState( "b", true )' ); + equal( c.str, undefined, '$.bbq.getState( "c" )' ); + equal( c.coerce, undefined, '$.bbq.getState( "c", true )' ); + equal( d.str, undefined, '$.bbq.getState( "d" )' ); + equal( d.coerce, undefined, '$.bbq.getState( "d", true )' ); + equal( e.str, undefined, '$.bbq.getState( "e" )' ); + equal( e.coerce, undefined, '$.bbq.getState( "e", true )' ); + equal( f.str, undefined, '$.bbq.getState( "f" )' ); + equal( f.coerce, undefined, '$.bbq.getState( "f", true )' ); + if ( !old_jquery ) { + deepEqual( event.getState(), {}, 'event.getState()' ); + deepEqual( event.getState(true), {}, 'event.getState( true )' ); + equal( event.getState('a'), undefined, 'event.getState( "a" )' ); + equal( event.getState('a',true), undefined, 'event.getState( "a", true )' ); + equal( event.getState('b'), undefined, 'event.getState( "b" )' ); + equal( event.getState('b',true), undefined, 'event.getState( "b", true )' ); + equal( event.getState('c'), undefined, 'event.getState( "c" )' ); + equal( event.getState('c',true), undefined, 'event.getState( "c", true )' ); + equal( event.getState('d'), undefined, 'event.getState( "d" )' ); + equal( event.getState('d',true), undefined, 'event.getState( "d", true )' ); + equal( event.getState('e'), undefined, 'event.getState( "e" )' ); + equal( event.getState('e',true), undefined, 'event.getState( "e", true )' ); + equal( event.getState('f'), undefined, 'event.getState( "f" )' ); + equal( event.getState('f',true), undefined, 'event.getState( "f", true )' ); + } + }, + + [ { a:'2', b:'2', c:true, d:false, e:'undefined', f:'' } ], + + [ { a:[4,5,6]}],//, b:{x:[7], y:8, z:[9,0,'true','false','undefined','']} }, 2 ], + + function(result){ + var b_str = old_jquery + ? '[object Object]' + : {x:['7'], y:'8', z:['9','0','true','false','undefined','']}, + b_coerce = old_jquery + ? '[object Object]' + : {x:[7], y:8};//z:[9,0,true,false,undefined,'']}; + + equal( hash_actual, '#!' + hash, 'hash should begin with #!' ); + deepEqual( obj.str, { a:['4','5','6'], b:b_str }, '$.bbq.getState()' ); + deepEqual( obj.coerce, { a:[4,5,6], b:b_coerce }, '$.bbq.getState( true )' ); + deepEqual( a.str, ['4','5','6'], '$.bbq.getState( "a" )' ); + deepEqual( a.coerce, [4,5,6], '$.bbq.getState( "a", true )' ); + if ( !old_jquery ) { + deepEqual( event.getState(), { a:['4','5','6'], b:b_str }, 'event.getState()' ); + deepEqual( event.getState(true), { a:[4,5,6], b:b_coerce }, 'event.getState( true )' ); + deepEqual( event.getState('a'), ['4','5','6'], 'event.getState( "a" )' ); + deepEqual( event.getState('a',true), [4,5,6], 'event.getState( "a", true )' ); + } + }, + + [ { a:'1', c:'2' }, 1 ], + + function(result){ + var b_str = old_jquery + ? '[object Object]' + : {x:['7'], y:'8', z:['9','0','true','false','undefined','']}, + b_coerce = old_jquery + ? '[object Object]' + : {x:[7], y:8};//, z:[9,0,true,false,undefined,'']}; + + equal( hash_actual, '#!' + hash, 'hash should begin with #!' ); + deepEqual( obj.str, { a:['4','5','6'], b:b_str, c:'2' }, '$.bbq.getState()' ); + deepEqual( obj.coerce, { a:[4,5,6], b:b_coerce, c:2 }, '$.bbq.getState( true )' ); + if ( !old_jquery ) { + deepEqual( event.getState(), { a:['4','5','6'], b:b_str, c:'2' }, 'event.getState()' ); + deepEqual( event.getState(true), { a:[4,5,6], b:b_coerce, c:2 }, 'event.getState( true )' ); + } + }, + + [ '#/path/to/file.php', 2 ], + + function(result){ + equal( hash_actual, '#!' + hash, 'hash should begin with #!' ); + equal( hash, '/path/to/file.php', '$.param.fragment()' ); + if ( !old_jquery ) { + equal( event.fragment, '/path/to/file.php', 'event.fragment' ); + } + }, + + [], + + function(result){ + equal( hash_actual, '#!', 'hash should just be #!' ); + equal( hash, '', '$.param.fragment()' ); + if ( !old_jquery ) { + equal( event.fragment, '', 'event.fragment' ); + } + }, + + function(result){ + $(window).bind( 'hashchange', function(evt){ + x = $.param.fragment(); + }); + }, + + [ '#omg_ponies', 2 ], + + function(result){ + equal( hash, 'omg_ponies', 'event handler 1: $.param.fragment()' ); + equal( x, 'omg_ponies', 'event handler 2: $.param.fragment()' ); + + hash = x = ''; + equal( hash + x, '', 'vars reset' ); + + $(window).triggerHandler( 'hashchange' ); + equal( hash, 'omg_ponies', 'event handler 1: $.param.fragment()' ); + equal( x, 'omg_ponies', 'event handler 2: $.param.fragment()' ); + + hash = x = ''; + equal( hash + x, '', 'vars reset' ); + + $(window).unbind( 'hashchange' ); + }, + + [ '#almost_done?not_search', 2 ], + + function(result){ + equal( hash, '', 'event handler 1: $.param.fragment()' ); + equal( x, '', 'event handler 2: $.param.fragment()' ); + + var events:any;// = $.data( window, 'events' ); + ok( !events || !events.hashchange, 'hashchange event unbound' ); + }, + + [ '#' ], + + function(result){ + x = []; + $(window).bind( 'hashchange', function(evt){ + x.push( $.param.fragment() ); + }); + }, + + function(result){ + !is_chrome && window.history.go( -1 ); + }, + + function(result){ + !is_chrome && window.history.go( -1 ); + }, + + function(result){ + !is_chrome && window.history.go( -1 ); + }, + + function(result){ + !is_chrome && window.history.go( -1 ); + }, + + function(result){ + if ( is_chrome ) { + // Read about this issue here: http://benalman.com/news/2009/09/chrome-browser-history-buggine/ + ok( true, 'history is sporadically broken in chrome, this is a known bug, so this test is skipped in chrome' ); + } else { + deepEqual( x, ['almost_done?not_search', 'omg_ponies', '', '/path/to/file.php'], 'back button and window.bbq.go(-1) should work' ); + } + + $(window).unbind( 'hashchange' ); + var events: any;// = $.data( window, 'events' ); + ok( !events || !events.hashchange, 'hashchange event unbound' ); + }, + + function(result){ + $.param.fragment.ajaxCrawlable( false ); + }, + + [ '#all_done' ] + + ); + +}); + + +}); // END CLOSURE \ No newline at end of file diff --git a/jquery.bbq/jquery.bbq.d.ts b/jquery.bbq/jquery.bbq.d.ts index b34ea0eb3..5e94efea9 100644 --- a/jquery.bbq/jquery.bbq.d.ts +++ b/jquery.bbq/jquery.bbq.d.ts @@ -1,42 +1,160 @@ // Type definitions for jquery.bbq 1.2 // Project: http://benalman.com/projects/jquery-bbq-plugin/ -// Definitions by: https://github.com/sunetos +// Definitions by: Adam R. Smith // Definitions: https://github.com/borisyankov/DefinitelyTyped -interface JQueryBBQ { - pushState(params?: any, merge_mode?: number): void; - getState(key?: string, coerce?: bool): any; - removeState(...key: any[]): void; +/// + +module JQueryBbq { + + interface JQuery { + /** + * Adds a 'state' into the browser history at the current position, setting + * location.hash and triggering any bound callbacks + * (provided the new state is different than the previous state). + * + * @name params A serialized params string or a hash string beginning with # to merge into location.hash. + * @name merge_mode Merge behavior defaults to 0 if merge_mode is not specified (unless a hash string beginning with # is specified, in which case merge behavior defaults to 2) + */ + pushState(params?: string, merge_mode?: number): void; + + pushState(params?: any, merge_mode?: number): void; + + /** + * Retrieves the current 'state' from the browser history, parsing + * location.hash for a specific key or returning an object containing the + * entire state, optionally coercing numbers, booleans, null and undefined + * values. + * + * @name key An optional state key for which to return a value. + * @name coerce If true, coerces any numbers or true, false, null, and undefined to their actual value. Defaults to false + */ + getState(key?: string, coerce?: bool): any; + + getState(coerce?: bool): any; + + /** + * Remove one or more keys from the current browser history 'state', creating + * a new state, setting location.hash and triggering any bound + * callbacks (provided the new state is different than + * the previous state). + * + * @name key One or more key values to remove from the current state. + */ + removeState(...key: any[]): void; + } + + interface ParamFragment { + (url?: string): string; + + (url: string, params: any, merge_mode?: number): string; + + /** + * Specify characters that will be left unescaped when fragments are created + * or merged using , or when the fragment is modified + * using . This option only applies to serialized data + * object fragments, and not set-as-string fragments. Does not affect the + * query string. Defaults to ",/" (comma, forward slash). + * + * @name chars The characters to not escape in the fragment. If unspecified, defaults to empty string (escape all characters). + */ + noEscape: (chars?: string) => void; + + /** + * TODO: DESCRIBE + * + * @name state TODO: DESCRIBE + */ + ajaxCrawlable(state?: bool): bool; + } + + interface JQueryDeparam { + /** + * Deserialize a params string into an object, optionally coercing numbers, + * booleans, null and undefined values; this method is the counterpart to the + * internal jQuery.param method. + * + * @name params A params string to be parsed. + * @name coerce If true, coerces any numbers or true, false, null, and undefined to their actual value. Defaults to false if omitted. + */ + (params: string, coerce?: bool): any; + + + /** + * Parse the query string from a URL or the current window.location.href, + * deserializing it into an object, optionally coercing numbers, booleans, + * null and undefined values. + * + * @name url An optional params string or URL containing query string params to be parsed. If url is omitted, the current window.location.href is used. + * @name coerce If true, coerces any numbers or true, false, null, and undefined to their actual value. Defaults to false if omitted. + */ + querystring(url?: string, coerce?: bool): any; + + /** + * Parse the fragment (hash) from a URL or the current window.location.href, + * deserializing it into an object, optionally coercing numbers, booleans, + * null and undefined values. + * + * @name url An optional params string or URL containing fragment (hash) params to be parsed. If url is omitted, the current window.location.href is used. + * @name coerce If true, coerces any numbers or true, false, null, and undefined to their actual value. Defaults to false if omitted. + */ + fragment(url?: string, coerce?: bool): any; + } + + interface EventObject extends JQueryEventObject { + fragment: string; + + getState( key?: string, coerce? :bool ); + } } interface JQueryParam { - (obj: any): string; - (obj: any, traditional: bool): string; + /** + * Parse the query string from a URL or the current window.location.href, + * deserializing it into an object, optionally coercing numbers, booleans, + * null and undefined values. + * + * @name url An optional params string or URL containing query string params to be parsed. If url is omitted, the current window.location.href is used. + * @name coerce (Boolean) If true, coerces any numbers or true, false, null, and undefined to their actual value. Defaults to false if omitted. + * @name merge_mode An object representing the deserialized params string. + */ + querystring(url?: string, coerce?: bool, merge_mode?: number): string; - querystring(url?: string): string; - querystring(url: string, params: any, merge_mode?: number): string; - fragment: { - noEscape: (chars?: string) => void; - (url?: string): string; - (url: string, params: any, merge_mode?: number): string; - }; -} + querystring(url?: string, coerce?: any, merge_mode?: number): string; -interface JQueryDeparam { - (params: string, coerce?: bool): any; - querystring(url?: string, coerce?: bool): any; - fragment(url?: string, coerce?: bool): any; + fragment: JQueryBbq.ParamFragment; + + /** + * Returns a params string equivalent to that returned by the internal + * jQuery.param method, but sorted, which makes it suitable for use as a + * cache key. + * + * @name obj An object to be serialized. + * @name traditional Params deep/shallow serialization mode. See the documentation at http://api.jquery.com/jQuery.param/ for more detail. + */ + sorted(obj: any, traditional?: bool): string; } interface JQueryStatic { - bbq: JQueryBBQ; - param: JQueryParam; - deparam: JQueryDeparam; + bbq: JQueryBbq.JQuery; - elemUrlAttr(tag_attr: any): any; + deparam: JQueryBbq.JQueryDeparam; + + /** + * Get the internal "Default URL attribute per tag" list, or augment the list + * with additional tag-attribute pairs, in case the defaults are insufficient. + * + * @name tag_attr An object containing a list of tag names and their associated default attribute names in the format { tag: 'attr', ... } to be merged into the internal tag-attribute list. + */ + elemUrlAttr(tag_attr?: any): any; } interface JQuery { - querystring(attr?: any, params?: any, merge_mode?: number): JQuery; - fragment(attr?: any, params?: any, merge_mode?: number): JQuery; + querystring(attr?: any, params?: any, merge_mode?: number): JQuery; + + fragment(attr?: any, params?: any, merge_mode?: number): JQuery; + + hashchange(eventData?: any, handler?: (eventObject: JQueryBbq.EventObject) => any): JQuery; + + hashchange(handler: (eventObject: JQueryBbq.EventObject) => any): JQuery; } diff --git a/jquery/jquery.d.ts b/jquery/jquery.d.ts index 36b46164b..cdae1d8ec 100644 --- a/jquery/jquery.d.ts +++ b/jquery/jquery.d.ts @@ -185,6 +185,11 @@ interface JQuerySupport { tbody?: bool; } +interface JQueryParam { + (obj: any): string; + (obj: any, traditional: bool): string; +} + /* Static members of jQuery (those on $ and jQuery themselves) */ @@ -208,8 +213,7 @@ interface JQueryStatic { getJSON(url: string, data?: any, success?: any): JQueryXHR; getScript(url: string, success?: any): JQueryXHR; - param(obj: any): string; - param(obj: any, traditional: bool): string; + param: JQueryParam; post(url: string, data?: any, success?: any, dataType?: any): JQueryXHR; @@ -296,6 +300,9 @@ interface JQueryStatic { contains(container: Element, contained: Element): bool; each(collection: any, callback: (indexInArray: any, valueOfElement: any) => any): any; + each(collection: JQuery, callback: (indexInArray: number, valueOfElement: HTMLElement) => any): any; + each(collection: string[], callback: (indexInArray: number, valueOfElement: string) => any): any; + each(collection: number[], callback: (indexInArray: number, valueOfElement: number) => any): any; extend(target: any, ...objs: any[]): Object; extend(deep: bool, target: any, ...objs: any[]): Object; @@ -375,7 +382,7 @@ interface JQuery { html(htmlString: string): JQuery; html(htmlContent: (index: number, oldhtml: string) => string): JQuery; - prop(propertyName: string): any; + prop(propertyName: string): string; prop(propertyName: string, value: any): JQuery; prop(map: any): JQuery; prop(propertyName: string, func: (index: any, oldPropertyValue: any) => any): JQuery; @@ -400,11 +407,11 @@ interface JQuery { /*** CSS ****/ - css(propertyName: string): any; - css(propertyNames: string[]): any; - css(properties: any): any; - css(propertyName: string, value: any): any; - css(propertyName: any, value: any): any; + css(propertyName: string): string; + css(propertyNames: string[]): string; + css(properties: any): JQuery; + css(propertyName: string, value: any): JQuery; + css(propertyName: any, value: any): JQuery; height(): number; height(value: number): JQuery; diff --git a/qunit/qunit.d.ts b/qunit/qunit.d.ts index f068a857c..b8ba3bb4a 100644 --- a/qunit/qunit.d.ts +++ b/qunit/qunit.d.ts @@ -5,44 +5,127 @@ interface DoneCallbackObject { + /** + * The number of failed assertions + */ failed: number; + + /** + * The number of passed assertions + */ passed: number; + + /** + * The total number of assertions + */ total: number; + + /** + * The time in milliseconds it took tests to run from start to finish. + */ runtime: number; } interface LogCallbackObject { + /** + * The boolean result of an assertion, true means passed, false means failed. + */ result: bool; + + /** + * One side of a comparision assertion. Can be undefined when ok() is used. + */ actual: Object; + + /** + * One side of a comparision assertion. Can be undefined when ok() is used. + */ expected: Object; + + /** + * A string description provided by the assertion. + */ message: string; + + /** + * The associated stacktrace, either from an exception or pointing to the source + * of the assertion. Depends on browser support for providing stacktraces, so can be + * undefined. + */ + source: string; } interface ModuleStartCallbackObject { + /** + * Name of the next module to run + */ name: string; } interface ModuleDoneCallbackObject { + /** + * Name of this module + */ name: string; + + /** + * The number of failed assertions + */ failed: number; + + /** + * The number of passed assertions + */ passed: number; + + /** + * The total number of assertions + */ total: number; } interface TestDoneCallbackObject { + /** + * TName of the next test to run + */ name: string; + + /** + * Name of the current module + */ module: string; + + /** + * The number of failed assertions + */ failed: number; + + /** + * The number of passed assertions + */ passed: number; + + /** + * The total number of assertions + */ total: number; + + /** + * The total runtime, including setup and teardown + */ + duration: number; } interface TestStartCallbackObject { + /** + * Name of the next test to run + */ name: string; + + /** + * Name of the current module + */ module: string; - failed: number; - passed: number; - total: number; } interface Config { @@ -56,7 +139,14 @@ interface Config { } interface LifecycleObject { + /** + * Runs before each test + */ setup?: () => any; + + /** + * Runs after each test + */ teardown?: () => any; } @@ -66,92 +156,554 @@ interface QUnitAssert { current_testEnvironment: any; jsDump: any; + /** + * A deep recursive comparison assertion, working on primitive types, arrays, objects, + * regular expressions, dates and functions. + * + * The deepEqual() assertion can be used just like equal() when comparing the value of + * objects, such that { key: value } is equal to { key: value }. For non-scalar values, + * identity will be disregarded by deepEqual. + * + * @param actual Object or Expression being tested + * @param expected Known comparison value + * @param message A short description of the assertion + */ deepEqual(actual: any, expected: any, message?: string); + + /** + * A non-strict comparison assertion, roughly equivalent to JUnit assertEquals. + * + * The equal assertion uses the simple comparison operator (==) to compare the actual + * and expected arguments. When they are equal, the assertion passes; otherwise, it fails. + * When it fails, both actual and expected values are displayed in the test result, + * in addition to a given message. + * + * @param actual Expression being tested + * @param expected Known comparison value + * @param message A short description of the assertion + */ equal(actual: any, expected: any, message?: string); + + /** + * An inverted deep recursive comparison assertion, working on primitive types, + * arrays, objects, regular expressions, dates and functions. + * + * The notDeepEqual() assertion can be used just like equal() when comparing the + * value of objects, such that { key: value } is equal to { key: value }. For non-scalar + * values, identity will be disregarded by notDeepEqual. + * + * @param actual Object or Expression being tested + * @param expected Known comparison value + * @param message A short description of the assertion + */ notDeepEqual(actual: any, expected: any, message?: string); + + /** + * A non-strict comparison assertion, checking for inequality. + * + * The notEqual assertion uses the simple inverted comparison operator (!=) to compare + * the actual and expected arguments. When they aren't equal, the assertion passes; + * otherwise, it fails. When it fails, both actual and expected values are displayed + * in the test result, in addition to a given message. + * + * @param actual Expression being tested + * @param expected Known comparison value + * @param message A short description of the assertion + */ notEqual(actual: any, expected: any, message?: string); + notPropEqual(actual: any, expected: any, message?: string); + propEqual(actual: any, expected: any, message?: string); + + /** + * A non-strict comparison assertion, checking for inequality. + * + * The notStrictEqual assertion uses the strict inverted comparison operator (!==) + * to compare the actual and expected arguments. When they aren't equal, the assertion + * passes; otherwise, it fails. When it fails, both actual and expected values are + * displayed in the test result, in addition to a given message. + * + * @param actual Expression being tested + * @param expected Known comparison value + * @param message A short description of the assertion + */ notStrictEqual(actual: any, expected: any, message?: string); + + /** + * A boolean assertion, equivalent to CommonJS’s assert.ok() and JUnit’s assertTrue(). + * Passes if the first argument is truthy. + * + * The most basic assertion in QUnit, ok() requires just one argument. If the argument + * evaluates to true, the assertion passes; otherwise, it fails. If a second message + * argument is provided, it will be displayed in place of the result. + * + * @param state Expression being tested + * @param message A short description of the assertion + */ ok(state: any, message?: string); + + /** + * A strict type and value comparison assertion. + * + * The strictEqual() assertion provides the most rigid comparison of type and value with + * the strict equality operator (===) + * + * @param actual Expression being tested + * @param expected Known comparison value + * @param message A short description of the assertion + */ strictEqual(actual: any, expected: any, message?: string); + + /** + * Assertion to test if a callback throws an exception when run. + * + * When testing code that is expected to throw an exception based on a specific set of + * circumstances, use throws() to catch the error object for testing and comparison. + * + * @param block Function to execute + * @param expected Error Object to compare + * @param message A short description of the assertion + */ throws(block: () => any, expected: any, message?: string); + + /** + * @param block Function to execute + * @param message A short description of the assertion + */ throws(block: () => any, message?: string); } interface QUnitStatic extends QUnitAssert{ /* ASYNC CONTROL */ + + /** + * Start running tests again after the testrunner was stopped. See stop(). + * + * When your async test has multiple exit points, call start() for the corresponding number of stop() increments. + * + * @param decrement Optional argument to merge multiple start() calls into one. Use with multiple corrsponding stop() calls. + */ start(decrement?: number); + + /** + * Stop the testrunner to wait for async tests to run. Call start() to continue. + * + * When your async test has multiple exit points, call stop() with the increment argument, corresponding to the number of start() calls you need. + * + * On Blackberry 5.0, window.stop is a native read-only function. If you deal with that browser, use QUnit.stop() instead, which will work anywhere. + * + * @param decrement Optional argument to merge multiple stop() calls into one. Use with multiple corrsponding start() calls. + */ stop(increment? : number); /* CALLBACKS */ + + /** + * Register a callback to fire whenever the test suite begins. + * + * QUnit.begin() is called once before running any tests. (a better would've been QUnit.start, + * but thats already in use elsewhere and can't be changed.) + * + * @param callback Callback to execute + */ begin(callback: () => any); + + /** + * Register a callback to fire whenever the test suite ends. + * + * @param callback Callback to execute. + */ done(callback: (details: DoneCallbackObject) => any); + + /** + * Register a callback to fire whenever an assertion completes. + * + * This is one of several callbacks QUnit provides. Its intended for integration scenarios like + * PhantomJS or Jenkins. The properties of the details argument are listed below as options. + * + * @param callback Callback to execute. + */ log(callback: (details: LogCallbackObject) => any); + + /** + * Register a callback to fire whenever a module ends. + * + * @param callback Callback to execute. + */ moduleDone(callback: (details: ModuleDoneCallbackObject) => any); + + /** + * Register a callback to fire whenever a module begins. + * + * @param callback Callback to execute. + */ moduleStart(callback: (details: ModuleStartCallbackObject) => any); + + /** + * Register a callback to fire whenever a test ends. + * + * @param callback Callback to execute. + */ testDone(callback: (details: TestDoneCallbackObject) => any); + + /** + * Register a callback to fire whenever a test begins. + * + * @param callback Callback to execute. + */ testStart(callback: (details: TestStartCallbackObject) => any); /* CONFIGURATION */ + + /** + * QUnit has a bunch of internal configuration defaults, some of which are + * useful to override. Check the description for each option for details. + */ config: Config; /* TEST */ + + /** + * Add an asynchronous test to run. The test must include a call to start(). + * + * For testing asynchronous code, asyncTest will automatically stop the test runner + * and wait for your code to call start() to continue. + * + * @param name Title of unit being tested + * @param expected Number of assertions in this test + * @param test Function to close over assertions + */ asyncTest(name: string, expected: number, test: () => any); + + /** + * Add an asynchronous test to run. The test must include a call to start(). + * + * For testing asynchronous code, asyncTest will automatically stop the test runner + * and wait for your code to call start() to continue. + * + * @param name Title of unit being tested + * @param test Function to close over assertions + */ asyncTest(name: string, test: () => any); + + /** + * Specify how many assertions are expected to run within a test. + * + * To ensure that an explicit number of assertions are run within any test, use + * expect( number ) to register an expected count. If the number of assertions + * run does not match the expected count, the test will fail. + * + * @param amount Number of assertions in this test. + */ expect(amount: number); + + /** + * Group related tests under a single label. + * + * All tests that occur after a call to module() will be grouped into that module. + * The test names will all be preceded by the module name in the test results. + * You can then use that module name to select tests to run. + * + * @param name Label for this group of tests + * @param lifecycle Callbacks to run before and after each test + */ module(name: string, lifecycle?: LifecycleObject); + + /** + * Add a test to run. + * + * When testing the most common, synchronous code, use test(). + * The assert argument to the callback contains all of QUnit's assertion methods. + * If you are avoiding using any of QUnit's globals, you can use the assert + * argument instead. + * + * @param title Title of unit being tested + * @param expected Number of assertions in this test + * @param test Function to close over assertions + */ test(title: string, expected: number, test: (assert: QUnitAssert) => any); + + /** + * @param title Title of unit being tested + * @param test Function to close over assertions + */ test(title: string, test: (assert: QUnitAssert) => any); - // https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L1568 + /** + * https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L1568 + */ equiv(a: any, b: any); // https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L661 raises: any; - // https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L897 + /** + * https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L897 + */ push(result, actual, expected, message): any; - // https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L839 + /** + * https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L839 + */ reset(): any; } /* ASSERT */ + +/** +* A deep recursive comparison assertion, working on primitive types, arrays, objects, +* regular expressions, dates and functions. +* +* The deepEqual() assertion can be used just like equal() when comparing the value of +* objects, such that { key: value } is equal to { key: value }. For non-scalar values, +* identity will be disregarded by deepEqual. +* +* @param actual Object or Expression being tested +* @param expected Known comparison value +* @param message A short description of the assertion +*/ declare function deepEqual(actual: any, expected: any, message?: string); + +/** +* A non-strict comparison assertion, roughly equivalent to JUnit assertEquals. +* +* The equal assertion uses the simple comparison operator (==) to compare the actual +* and expected arguments. When they are equal, the assertion passes; otherwise, it fails. +* When it fails, both actual and expected values are displayed in the test result, +* in addition to a given message. +* +* @param actual Expression being tested +* @param expected Known comparison value +* @param message A short description of the assertion +*/ declare function equal(actual: any, expected: any, message?: string); + +/** +* An inverted deep recursive comparison assertion, working on primitive types, +* arrays, objects, regular expressions, dates and functions. +* +* The notDeepEqual() assertion can be used just like equal() when comparing the +* value of objects, such that { key: value } is equal to { key: value }. For non-scalar +* values, identity will be disregarded by notDeepEqual. +* +* @param actual Object or Expression being tested +* @param expected Known comparison value +* @param message A short description of the assertion +*/ declare function notDeepEqual(actual: any, expected: any, message?: string); + +/** +* A non-strict comparison assertion, checking for inequality. +* +* The notEqual assertion uses the simple inverted comparison operator (!=) to compare +* the actual and expected arguments. When they aren't equal, the assertion passes; +* otherwise, it fails. When it fails, both actual and expected values are displayed +* in the test result, in addition to a given message. +* +* @param actual Expression being tested +* @param expected Known comparison value +* @param message A short description of the assertion +*/ declare function notEqual(actual: any, expected: any, message?: string); + +/** +* A non-strict comparison assertion, checking for inequality. +* +* The notStrictEqual assertion uses the strict inverted comparison operator (!==) +* to compare the actual and expected arguments. When they aren't equal, the assertion +* passes; otherwise, it fails. When it fails, both actual and expected values are +* displayed in the test result, in addition to a given message. +* +* @param actual Expression being tested +* @param expected Known comparison value +* @param message A short description of the assertion +*/ declare function notStrictEqual(actual: any, expected: any, message?: string); + +/** +* A boolean assertion, equivalent to CommonJS’s assert.ok() and JUnit’s assertTrue(). +* Passes if the first argument is truthy. +* +* The most basic assertion in QUnit, ok() requires just one argument. If the argument +* evaluates to true, the assertion passes; otherwise, it fails. If a second message +* argument is provided, it will be displayed in place of the result. +* +* @param state Expression being tested +* @param message A short description of the assertion +*/ declare function ok(state: any, message?: string); + +/** +* A strict type and value comparison assertion. +* +* The strictEqual() assertion provides the most rigid comparison of type and value with +* the strict equality operator (===) +* +* @param actual Expression being tested +* @param expected Known comparison value +* @param message A short description of the assertion +*/ declare function strictEqual(actual: any, expected: any, message?: string); + +/** +* Assertion to test if a callback throws an exception when run. +* +* When testing code that is expected to throw an exception based on a specific set of +* circumstances, use throws() to catch the error object for testing and comparison. +* +* @param block Function to execute +* @param expected Error Object to compare +* @param message A short description of the assertion +*/ declare function throws(block: () => any, expected: any, message?: string); + +/** +* @param block Function to execute +* @param message A short description of the assertion +*/ declare function throws(block: () => any, message?: string); /* ASYNC CONTROL */ + +/** +* Start running tests again after the testrunner was stopped. See stop(). +* +* When your async test has multiple exit points, call start() for the corresponding number of stop() increments. +* +* @param decrement Optional argument to merge multiple start() calls into one. Use with multiple corrsponding stop() calls. +*/ declare function start(decrement?: number); + +/** +* Stop the testrunner to wait for async tests to run. Call start() to continue. +* +* When your async test has multiple exit points, call stop() with the increment argument, corresponding to the number of start() calls you need. +* +* On Blackberry 5.0, window.stop is a native read-only function. If you deal with that browser, use QUnit.stop() instead, which will work anywhere. +* +* @param decrement Optional argument to merge multiple stop() calls into one. Use with multiple corrsponding start() calls. +*/ declare function stop(increment? : number); /* CALLBACKS */ + +/** +* Register a callback to fire whenever the test suite begins. +* +* QUnit.begin() is called once before running any tests. (a better would've been QUnit.start, +* but thats already in use elsewhere and can't be changed.) +* +* @param callback Callback to execute +*/ declare function begin(callback: () => any); + +/** +* Register a callback to fire whenever the test suite ends. +* +* @param callback Callback to execute. +*/ declare function done(callback: (details: DoneCallbackObject) => any); + +/** +* Register a callback to fire whenever an assertion completes. +* +* This is one of several callbacks QUnit provides. Its intended for integration scenarios like +* PhantomJS or Jenkins. The properties of the details argument are listed below as options. +* +* @param callback Callback to execute. +*/ declare function log(callback: (details: LogCallbackObject) => any); + +/** +* Register a callback to fire whenever a module ends. +* +* @param callback Callback to execute. +*/ declare function moduleDone(callback: (details: ModuleDoneCallbackObject) => any); + +/** +* Register a callback to fire whenever a module begins. +* +* @param callback Callback to execute. +*/ declare function moduleStart(callback: (name: string) => any); + +/** +* Register a callback to fire whenever a test ends. +* +* @param callback Callback to execute. +*/ declare function testDone(callback: (details: TestDoneCallbackObject) => any); + +/** +* Register a callback to fire whenever a test begins. +* +* @param callback Callback to execute. +*/ declare function testStart(callback: (details: TestStartCallbackObject) => any); /* TEST */ + +/** +* Add an asynchronous test to run. The test must include a call to start(). +* +* For testing asynchronous code, asyncTest will automatically stop the test runner +* and wait for your code to call start() to continue. +* +* @param name Title of unit being tested +* @param expected Number of assertions in this test +* @param test Function to close over assertions +*/ declare function asyncTest(name: string, expected?: any, test?: () => any); + +/** +* Add an asynchronous test to run. The test must include a call to start(). +* +* For testing asynchronous code, asyncTest will automatically stop the test runner +* and wait for your code to call start() to continue. +* +* @param name Title of unit being tested +* @param test Function to close over assertions +*/ +declare function asyncTest(name: string, test: () => any); + +/** +* Specify how many assertions are expected to run within a test. +* +* To ensure that an explicit number of assertions are run within any test, use +* expect( number ) to register an expected count. If the number of assertions +* run does not match the expected count, the test will fail. +* +* @param amount Number of assertions in this test. +*/ declare function expect(amount: number); // ** conflict with TypeScript module keyword. Must be used on QUnit namespace //declare var module: (name: string, lifecycle?: LifecycleObject) => any; +/** +* Add a test to run. +* +* When testing the most common, synchronous code, use test(). +* The assert argument to the callback contains all of QUnit's assertion methods. +* If you are avoiding using any of QUnit's globals, you can use the assert +* argument instead. +* +* @param title Title of unit being tested +* @param expected Number of assertions in this test +* @param test Function to close over assertions +*/ declare function test(title: string, expected: number, test: (assert?: QUnitAssert) => any); + +/** +* @param title Title of unit being tested +* @param test Function to close over assertions +*/ declare function test(title: string, test: (assert?: QUnitAssert) => any); declare function notPropEqual(actual: any, expected: any, message?: string); + declare function propEqual(actual: any, expected: any, message?: string); // https://github.com/jquery/qunit/blob/master/qunit/qunit.js#L1568 diff --git a/requirejs/require.d.ts b/requirejs/require.d.ts index 1d6934041..e9351aa46 100644 --- a/requirejs/require.d.ts +++ b/requirejs/require.d.ts @@ -202,5 +202,5 @@ interface RequireDefine { } // Ambient declarations for 'require' and 'define' -var require: Require; -var define: RequireDefine; +declare var require: Require; +declare var define: RequireDefine; diff --git a/restify/restify-test.ts b/restify/restify-test.ts new file mode 100644 index 000000000..964a428b5 --- /dev/null +++ b/restify/restify-test.ts @@ -0,0 +1,218 @@ +/// + +import restify = module("restify"); + +var server = restify.createServer({ + formatters: { + 'application/foo': function formatFoo(req, res, body) { + if (body instanceof Error) + return body.stack; + + if (body) + return body.toString('base64'); + + return body; + } + } +}); + +server = restify.createServer({ + certificate: "test", + key: "test", + formatters: {}, + log: {}, + name: "test", + spdy: {}, + version: "", + responseTimeHeader: "", + responseTimeFormatter : (durationInMilliseconds: number) => {} +}); + +server.on('someEvent', ()=>{}); + + +server.use((req, res, next)=>{}); +server.use([(req, res, next)=>{}]); +server.use((req, res, next)=>{}, (req, res, next)=>{}); + +function send(req, res, next) { + req.header('key', 'val'); + req.header('key') === 'val'; + + req.accepts('test') === true; + req.is('test') === true; + + req.getLogger('test'); + + var log = req.log; + log.debug({params: req.params}, 'Hello there %s', 'foo'); + + req.contentLength === 50; + req.contentType === 'test'; + req.href === 'test'; + req.id === 'test'; + req.path === 'test'; + req.query === 'test'; + req.secure === true; + req.time === 50; + req.params; + + res.header('test'); + res.header('test', {}); + res.header('test', new Date()); + + res.cache(); + res.cache('testst', {}); + + res.status(344); + + res.send({hello: 'world'}); + res.send(201, {hello: 'world'}); + res.send(new restify.BadRequestError('meh')); + + res.json(201, {hello: 'world'}); + res.json({hello: 'world'}); + + res.code === 50; + res.contentLength === 50; + res.charSet === 'test'; + res.contentType === 'test'; + res.headers; + res.id === 'test'; + + res.send('hello ' + req.params.name); + return next(); +} + + +server.post('/hello', send); +server.put( '/hello', send); +server.del( '/hello', send); +server.get( '/hello', send); +server.head('/hello', send); + +server.post(/(.*)/, send); +server.put( /(.*)/, send); +server.del( /(.*)/, send); +server.get( /(.*)/, send); +server.head(/(.*)/, send); + +new restify.ConflictError("test"); +new restify.InvalidArguementError("message"); +new restify.RestError("message"); +new restify.BadDigestError("message"); +new restify.BadMethodError("message"); +new restify.BadRequestError('test'); +new restify.InternalError("message"); +new restify.InvalidContentError("message"); +new restify.InvalidCredentialsError("message"); +new restify.InvalidHeaderError("message"); +new restify.InvalidVersionError("message"); +new restify.MissingParameterError("message"); +new restify.NotAuthorizedError("message"); +new restify.RequestExpiredError("jjmessage"); +new restify.RequestThrottledError("message"); +new restify.ResourceNotFoundError("message"); +new restify.WrongAcceptError("message"); + +server.name = ""; +server.version = ""; +server.log = {}; +server.acceptable = ["test"]; +server.url = ""; + +server.address().port; +server.address().family; +server.address().address; + +server.listen("somePath", send); +server.close(); + +server.use(restify.acceptParser(server.acceptable)); +server.use(restify.authorizationParser()); +server.use(restify.dateParser()); +server.use(restify.queryParser()); +server.use(restify.jsonp()); +server.use(restify.gzipResponse()); +server.use(restify.bodyParser()); +server.use(restify.throttle({ + burst: 100, + rate: 50, + ip: true, + overrides: { + '192.168.1.1': { + rate: 0, + burst: 0 + } + } +})); + +server.on('after', restify.auditLogger({ + log: ()=>{} +})); + +restify.defaultResponseHeaders = function(data) { + this.header('Server', 'helloworld'); +}; + +restify.defaultResponseHeaders = false; + +//RESTIFY Client Tests + +var client = restify.createJsonClient({ + url: 'https://api.us-west-1.joyentcloud.com', + version: '*' +}); + +client = restify.createStringClient({ + accept: "test", + connectTimeout: 30, + dtrace: {}, + gzip: {}, + headers: {}, + log: {}, + retry: {}, + signRequest: ()=>{}, + url: "", + userAgent: "", + version: "" +}); + +client.get("test", send); +client.head('test', send); +client.post('path', {}, send); +client.put('path', {}, send); +client.del('path', send); + +client.post('/foo', { hello: 'world' }, function(err, req, res, obj) { + console.log('%d -> %j', res.statusCode, res.headers); + console.log('%j', obj); +}); + +client.get('/foo/bar', function(err, req, res, data) { + console.log('%s', data); +}); + +var client2 = restify.createClient({ + url: 'http://127.0.0.1' +}); + +client2.get('/str/mcavage', function(err, req) { + + req.on('result', function(err, res) { + + res.body = ''; + res.setEncoding('utf8'); + res.on('data', function(chunk) { + res.body += chunk; + }); + + res.on('end', function() { + console.log(res.body); + }); + }); +}); + + +client.basicAuth('test', 'password'); +client2.basicAuth('test', 'password'); diff --git a/restify/restify.d.ts b/restify/restify.d.ts new file mode 100644 index 000000000..9d5edd93d --- /dev/null +++ b/restify/restify.d.ts @@ -0,0 +1,152 @@ +interface addressInterface { + port: number; + family: string; + address: string; +} + +interface Request { + header: (key: string, defaultValue?: string) => any; + accepts: (type: string) => bool; + is: (type: string) => bool; + getLogger: (component: string) => any; + contentLength: number; + contentType: string; + href: string; + log: Object; + id: string; + path: string; + query: string; + secure: bool; + time: number; + params: any; +} + +interface Response { + header: (key: string, value ?: any) => any; + cache: (type?: any, options?: Object) => any; + status: (code: number) => any; + send: (status?: any, body?: any) => any; + json: (status?: any, body?: any) => any; + code: number; + contentLength: number; + charSet: string; + contentType: string; + headers: Object; + statusCode: number; + id: string; +} + +interface Server { + use: (... handler: any[]) => any; + post: (route: any, routeCallBack: (req: Request, res: Response, next: Function) => any) => any; + put: (route: any, routeCallBack: (req: Request, res: Response, next: Function) => any) => any; + del: (route: any, routeCallBack: (req: Request, res: Response, next: Function) => any) => any; + get: (route: any, routeCallBack: (req: Request, res: Response, next: Function ) => any) => any; + head: (route: any, routeCallBack: (req: Request, res: Response, next: Function) => any) => any; + on: (event: string, callback: Function) => any; + name: string; + version: string; + log: Object; + acceptable: string[]; + url: string; + address: () => addressInterface; + listen: (... args: any[]) => any; + close: (... args: any[]) => any; + pre: (routeCallBack: (req: Request, res: Response, next: Function) => any) => any; + +} + +interface ServerOptions { + certificate ?: string; + key ?: string; + formatters ?: Object; + log ?: Object; + name ?: string; + spdy ?: Object; + version ?: string; + responseTimeHeader ?: string; + responseTimeFormatter ?: (durationInMilliseconds: number) => any; +} + +interface ClientOptions { + accept?: string; + connectTimeout?: number; + dtrace?: Object; + gzip?: Object; + headers?: Object; + log?: Object; + retry?: Object; + signRequest?: Function; + url?: string; + userAgent?: string; + version?: string; +} + +interface Client { + get: (path: string, callback?: (err: any, req: Request, res: Response, obj: any) => any) => any; + head: (path: string, callback?: (err: any, req: Request, res: Response) => any) => any; + post: (path: string, object: any, callback?: (err: any, req: Request, res: Response, obj: any) => any) => any; + put: (path: string, object: any, callback?: (err: any, req: Request, res: Response, obj: any) => any) => any; + del: (path: string, callback?: (err: any, req: Request, res: Response) => any) => any; + basicAuth: (username: string, password: string) => any; +} + +interface HttpClient extends Client { + get: (path?: any, callback?: Function) => any; + head: (path?:any, callback?: Function) => any; + post: (opts?: any, callback?: Function) => any; + put: (opts?: any, callback?: Function) => any; + del: (opts?: any, callback?: Function) => any; +} + +interface ThrottleOptions { + burst?: number; + rate?: number; + ip?: bool; + xff?: bool; + username?: bool; + tokensTable?: Object; + maxKeys?: number; + overrides?: Object; +} + +declare module "restify" { + export function createServer(options?: ServerOptions): Server; + + export function createJsonClient(options?: ClientOptions): Client; + export function createStringClient(options?: ClientOptions): Client; + export function createClient(options?: ClientOptions): HttpClient; + + export class ConflictError { constructor(message?: any); }; + export class InvalidArguementError { constructor(message?: any); }; + export class RestError { constructor(message?: any); }; + export class BadDigestError { constructor(message: any); }; + export class BadMethodError { constructor(message: any); }; + export class BadRequestError { constructor(message: any); }; + export class InternalError { constructor(message: any); }; + export class InvalidContentError { constructor(message: any); }; + export class InvalidCredentialsError { constructor(message: any); }; + export class InvalidHeaderError { constructor(message: any); }; + export class InvalidVersionError { constructor(message: any); }; + export class MissingParameterError { constructor(message: any); }; + export class NotAuthorizedError { constructor(message: any); }; + export class RequestExpiredError { constructor(message: any); }; + export class RequestThrottledError { constructor(message: any); }; + export class ResourceNotFoundError { constructor(message: any); }; + export class WrongAcceptError { constructor(message: any); }; + + export function acceptParser(parser: any); + export function authorizationParser(); + export function dateParser(skew?: number); + export function queryParser(options?: Object); + export function urlEncodedBodyParser(options?: Object); + export function jsonp(options?: Object); + export function gzipResponse(options?: Object); + export function bodyParser(options?: Object); + export function requestLogger(options?: Object); + export function serveStatic(options?: Object); + export function throttle(options?: ThrottleOptions); + export function conditionalRequest(options?: Object); + export function auditLogger(options?: Object); + export var defaultResponseHeaders : any; +} diff --git a/videojs/videojs-tests.ts b/videojs/videojs-tests.ts index 37f8d5fda..7995339ac 100644 --- a/videojs/videojs-tests.ts +++ b/videojs/videojs-tests.ts @@ -1,7 +1,6 @@ // Tests for Video.js API /// -<<<<<<< HEAD _V_("example_video_1").ready(function(){ var myPlayer:VideoJSPlayer = this; @@ -72,7 +71,4 @@ _V_("example_video_1").ready(function(){ myPlayer.addEvent("volumechange", myFunc); myPlayer.removeEvent("volumechange", myFunc); -}); -======= -var myPlayer:VideoJSPlayer = _V_("example_video_1"); ->>>>>>> f8b142ba4c1a906b03601cd1f0d53484250f11b3 +}); \ No newline at end of file diff --git a/videojs/videojs.d.ts b/videojs/videojs.d.ts index 17834c22e..49fb1c09a 100644 --- a/videojs/videojs.d.ts +++ b/videojs/videojs.d.ts @@ -26,10 +26,7 @@ interface VideoJSPlayer { src(newSource: VideoJSSource): VideoJSPlayer; src(newSource: VideoJSSource[]): VideoJSPlayer; currentTime(seconds: number): VideoJSPlayer; -<<<<<<< HEAD currentTime(): number; -======= ->>>>>>> f8b142ba4c1a906b03601cd1f0d53484250f11b3 duration(): number; buffered(): TimeRanges; bufferedPercent(): number;