1 |
|
2 |
|
3 |
|
4 |
|
5 | !function(describe) {
|
6 | var assert = describe.assert
|
7 | , GLOBAL = {}
|
8 |
|
9 | assert.wait = function() {
|
10 | var k
|
11 | , obj = this
|
12 | , hooks = []
|
13 | , hooked = []
|
14 |
|
15 | for (k in obj) if (typeof obj[k] === "function") swap(k)
|
16 | function swap(k) {
|
17 | hooked.push(k, obj[k])
|
18 | obj[k] = function() {
|
19 | hooks.push(k, arguments)
|
20 | return obj
|
21 | }
|
22 | }
|
23 |
|
24 | return function() {
|
25 | if (!hooks) return
|
26 | for (var v, scope = obj, i = hooked.length; i--; i--) {
|
27 | obj[hooked[i-1]] = hooked[i]
|
28 | }
|
29 |
|
30 | for (; (v = hooks[++i]); ) {
|
31 | scope = scope[v].apply(scope, hooks[++i]) || scope
|
32 | }
|
33 | hooks = hooked = null
|
34 | }
|
35 | }
|
36 | assert.open = function(url, replace) {
|
37 | history.setUrl(url, replace)
|
38 | return this
|
39 | }
|
40 |
|
41 | assert.resizeTo = function(width, height) {
|
42 | El.css({ width: width, height: height })
|
43 | View.emit("resize")
|
44 | return this
|
45 | }
|
46 |
|
47 | assert.waitTill = function(fn, options) {
|
48 | var result
|
49 | , testCase = this
|
50 | , count = 30
|
51 | , resume = testCase.wait()
|
52 |
|
53 | if (options.timeout) {
|
54 | count = 0 | (options.timeout / 50)
|
55 | }
|
56 |
|
57 | this.ok(function() {
|
58 | return !!result
|
59 | }, options || "Function returns something")
|
60 | test()
|
61 |
|
62 | return testCase
|
63 |
|
64 | function test() {
|
65 | result = fn()
|
66 | if (!result && count--) return setTimeout(test, 50)
|
67 | testCase.ok(result)
|
68 | resume()
|
69 | }
|
70 | }
|
71 | assert.viewOpen = function(route, options) {
|
72 | return this.waitTill(function() {
|
73 | return View(route).open
|
74 | }, options || "View " + route + " should be open")
|
75 | }
|
76 | assert.waitSelector = function(sel, options) {
|
77 | return this.waitTill(function() {
|
78 | return document.body.find.call(document.documentElement, sel)
|
79 | }, options || "Selector "+sel+" should be in dom")
|
80 | }
|
81 | assert.countSelectors = function(sel, expected, options) {
|
82 | this.waitSelector(sel, options)
|
83 | this.ok(function() {
|
84 | var nodes = document.body.findAll(sel)
|
85 | , count = nodes && nodes.length
|
86 | return count === expected
|
87 | }, options || "Number of matches for " + sel + " should be " + expected)
|
88 | return this
|
89 | }
|
90 | assert.haveText = function(sel, expected, options) {
|
91 | this.waitSelector(sel, options)
|
92 | this.waitTill(function() {
|
93 | var node = document.body.find(sel)
|
94 | , txt = node && node[node.tagName == "INPUT" ? "val" : "txt"]().trim()
|
95 | return txt === expected
|
96 | }, options || sel + " should have text: " + expected)
|
97 | return this
|
98 | }
|
99 | assert.fill = function(sel, expected, options) {
|
100 | this.waitSelector(sel, options)
|
101 | this.ok(function() {
|
102 | var node = document.body.find(sel)
|
103 | , val = node && node.val(expected)
|
104 | return val === expected
|
105 | }, options || sel + " should have value " + expected)
|
106 | return this
|
107 | }
|
108 | assert.click = function(sel, expected, options) {
|
109 | this.waitSelector(sel, options)
|
110 | this.ok(function() {
|
111 | var ev
|
112 | , node = document.body.find(sel)
|
113 | , pointerX = 0
|
114 | , pointerY = 0
|
115 | , button = 0
|
116 | , ctrlKey = false
|
117 | , altKey = false
|
118 | , shiftKey = false
|
119 | , metaKey = false
|
120 |
|
121 | if (node) {
|
122 | if (node.dispatchEvent) {
|
123 | ev = document.createEvent("MouseEvents")
|
124 | ev.initMouseEvent("click", true, true, document.defaultView, button,
|
125 | pointerX, pointerY, pointerX, pointerY,
|
126 | ctrlKey, altKey, shiftKey, metaKey,
|
127 | button, node)
|
128 | node.dispatchEvent(ev)
|
129 | } else if (node.click) {
|
130 | node.click()
|
131 | } else if (node.fireEvent) {
|
132 | node.fireEvent("onclick")
|
133 | } else if (typeof node.onclick == "function") {
|
134 | node.onclick()
|
135 | }
|
136 | }
|
137 | return !!node
|
138 | }, options || sel + " should be clickable")
|
139 | return this
|
140 | }
|
141 | assert.collectCssUsage = function(options) {
|
142 | options = options || {}
|
143 | var selectors = GLOBAL.selectorsUsage = {}
|
144 | , styleSheets = document.styleSheets
|
145 | , styleSheetsCount = styleSheets.length
|
146 | , ignoreFiles = options.ignoreFiles
|
147 | , ignoreSelectors = options.ignoreSelectors
|
148 | , cleanSelectorRe = /:(?:focus|active|hover|unknown|:[-\w]+)\b/g
|
149 |
|
150 | while (styleSheetsCount--) {
|
151 | parseStyleSheet(styleSheets[styleSheetsCount])
|
152 | }
|
153 |
|
154 | function parseStyleSheet(styleSheet) {
|
155 | var rule
|
156 | , rules = styleSheet.cssRules || styleSheet.rules
|
157 | , rulesCount = rules.length
|
158 | , fileName = styleSheet.href
|
159 |
|
160 |
|
161 | if (/^\w+:\/\//.test(fileName)) {
|
162 | fileName = relative(location.href.replace(/\/[^\/]*$/, ""), styleSheet.href||"")
|
163 | }
|
164 |
|
165 | if (ignoreFiles && ignoreFiles.indexOf(fileName) > -1) return
|
166 |
|
167 |
|
168 | if (styleSheet.imports) {
|
169 | for (var i = styleSheet.imports.length; i--; ) {
|
170 | parseStyleSheet(styleSheet.imports[i])
|
171 | }
|
172 | }
|
173 |
|
174 | while (rulesCount--) {
|
175 | rule = rules[rulesCount]
|
176 | if (rule.styleSheet) {
|
177 | parseStyleSheet(rule.styleSheet)
|
178 | } else if (rule.selectorText) {
|
179 | rule.selectorText.split(/\s*,\s*/).each(selFn)
|
180 | } else {
|
181 |
|
182 | }
|
183 | }
|
184 | function selFn(sel) {
|
185 | sel = sel.replace(cleanSelectorRe, "").toLowerCase()
|
186 | if (!sel || ignoreSelectors && ignoreSelectors.indexOf(sel) > -1) {
|
187 | return
|
188 | }
|
189 | selectors[sel] = selectors[sel] || {files: [], count: 0}
|
190 | if (selectors[sel].files.indexOf(fileName + ":" + rulesCount) == -1) {
|
191 | selectors[sel].files.unshift(fileName + ":" + rulesCount)
|
192 | }
|
193 | }
|
194 | }
|
195 | View.on("show", count)
|
196 | function count() {
|
197 | var sel
|
198 | , arr = Object.keys(selectors)
|
199 | , len = arr.length
|
200 |
|
201 | for (; (sel = arr[--len]); ) {
|
202 | selectors[sel].count += document.body.findAll.call(document.documentElement, sel).length
|
203 | }
|
204 | }
|
205 | return this
|
206 | }
|
207 |
|
208 | assert.assertCssUsage = function(options) {
|
209 | options = options || {}
|
210 | var assert = this.test(options.message || "it should use all css rules")
|
211 |
|
212 | var sel
|
213 | , selectors = GLOBAL.selectorsUsage
|
214 | , arr = Object.keys(selectors)
|
215 | , len = arr.length
|
216 |
|
217 | assert.plan(len)
|
218 | assert.options.noStack = true
|
219 |
|
220 | for (; (sel = arr[--len]); ) {
|
221 | assert.ok(selectors[sel].count, "Unused rule '" + sel + "' in " + selectors[sel].files)
|
222 | }
|
223 | return assert
|
224 | }
|
225 |
|
226 | assert.collectViewsUsage = function() {
|
227 | var usage = GLOBAL.viewsUsage = {}
|
228 |
|
229 | View.on("show", function(route) {
|
230 | var view = View.views[route]
|
231 | for(; (usage[route] = usage[route]||0), usage[route]++, (route = (view = view.parent||{}).route); );
|
232 | })
|
233 | return this
|
234 | }
|
235 |
|
236 | assert.assertViewsUsage = function() {
|
237 | var assert = this.it("should use all views")
|
238 | var route
|
239 | , routes = Object.keys(View.views)
|
240 | , len = routes.length
|
241 | , usage = GLOBAL.viewsUsage
|
242 |
|
243 | assert.plan(len)
|
244 | assert.options.noStack = true
|
245 |
|
246 | for (; (route = routes[--len]); ) {
|
247 | assert.ok(usage[route], "Unused view " + route)
|
248 | }
|
249 | return assert
|
250 | }
|
251 |
|
252 | function clear(path) {
|
253 | if (typeof path != "string") {
|
254 | throw new TypeError("Path must be a string. Received " + typeof path)
|
255 | }
|
256 | return path.replace(/\/+$/, "")
|
257 | }
|
258 | var normalizeRe = /^\.\/|(?:^\/\.\.|\/)(\/)|\/(?:[^\/]*\/\.)?\.(\/|$)/
|
259 |
|
260 | function normalize(path) {
|
261 | for (; path != (path = path.replace(normalizeRe, "$1$2")); );
|
262 | return path
|
263 | }
|
264 |
|
265 | function relative(from, to) {
|
266 | from = normalize(clear(from))
|
267 | to = normalize(clear(to))
|
268 |
|
269 | if (from === to) return ""
|
270 |
|
271 | from = from.split("/")
|
272 | to = to.split("/")
|
273 |
|
274 | for (var i = from.length, common = i; i--; ) {
|
275 | if (from[i] !== to[i]) common = i
|
276 | from[i] = ".."
|
277 | }
|
278 |
|
279 | return from.slice(common).concat(to.slice(common)).join("/")
|
280 | }
|
281 |
|
282 | assert.isVisible = function(el) {
|
283 | return this.ok(el.offsetWidth > 0 && el.offsetHeight > 0)
|
284 | }
|
285 |
|
286 | }(describe)
|
287 |
|
288 |
|