From 7dee3d0e83785bb22485e0923e366f61beef5f39 Mon Sep 17 00:00:00 2001 From: Fraham Date: Thu, 23 Nov 2017 20:10:51 +0000 Subject: [PATCH 01/27] Merge --- index.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/index.html b/index.html index 01f1366..7bc4ec9 100644 --- a/index.html +++ b/index.html @@ -649,10 +649,7 @@ function add(x, y) { <button popover-top="Popover on top">Popover on top and on a button!</button> -<<<<<<< HEAD -======= - ->>>>>>> refs/remotes/rhyneav/master +

Cards

Full card example

From c1da0dc912bfb475966b2751e360af4a14e3d673 Mon Sep 17 00:00:00 2001 From: Fraham Date: Mon, 27 Nov 2017 23:06:12 +0000 Subject: [PATCH 02/27] Test Setup --- .gitignore | 3 +- tests/build/config/build_command.js | 16 + tests/build/config/jshint.conf.js | 66 + tests/build/config/karma.conf.js | 75 + tests/build/config/paths.js | 20 + tests/build/config/tested_browsers.js | 17 + tests/build/scripts/build.jakefile.js | 128 + tests/build/scripts/run_jake.bat | 7 + tests/build/scripts/run_jake.sh | 6 + tests/build/scripts/watch.js | 28 + tests/build/util/browserify_runner.js | 21 + tests/build/util/karma_runner.js | 76 + tests/build/util/mocha_runner.js | 54 + tests/build/util/sh.js | 48 + tests/build/util/version_checker.js | 29 + tests/jake.bat | 1 + tests/jake.sh | 2 + tests/package.json | 33 + tests/quixote.js | 4195 +++++++++++++++++++ tests/src/_media_css_test.js | 135 + tests/src/icon.svg | 17 + tests/src/index.html | 28 + tests/vendor/chai-2.1.0.js | 5332 +++++++++++++++++++++++++ tests/vendor/quixote.js | 4195 +++++++++++++++++++ 24 files changed, 14531 insertions(+), 1 deletion(-) create mode 100644 tests/build/config/build_command.js create mode 100644 tests/build/config/jshint.conf.js create mode 100644 tests/build/config/karma.conf.js create mode 100644 tests/build/config/paths.js create mode 100644 tests/build/config/tested_browsers.js create mode 100644 tests/build/scripts/build.jakefile.js create mode 100644 tests/build/scripts/run_jake.bat create mode 100644 tests/build/scripts/run_jake.sh create mode 100644 tests/build/scripts/watch.js create mode 100644 tests/build/util/browserify_runner.js create mode 100644 tests/build/util/karma_runner.js create mode 100644 tests/build/util/mocha_runner.js create mode 100644 tests/build/util/sh.js create mode 100644 tests/build/util/version_checker.js create mode 100644 tests/jake.bat create mode 100644 tests/jake.sh create mode 100644 tests/package.json create mode 100644 tests/quixote.js create mode 100644 tests/src/_media_css_test.js create mode 100644 tests/src/icon.svg create mode 100644 tests/src/index.html create mode 100644 tests/vendor/chai-2.1.0.js create mode 100644 tests/vendor/quixote.js diff --git a/.gitignore b/.gitignore index 8774cec..1778ff3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /node_modules npm-debug.log -/dist \ No newline at end of file +/dist +/tests/node_modules \ No newline at end of file diff --git a/tests/build/config/build_command.js b/tests/build/config/build_command.js new file mode 100644 index 0000000..c946448 --- /dev/null +++ b/tests/build/config/build_command.js @@ -0,0 +1,16 @@ +// Copyright (c) 2012 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details. + +// A cross-platform mechanism for determining how to run the build. +(function() { + "use strict"; + + var UNIX_BUILD_COMMAND = "./jake.sh"; + var WINDOWS_BUILD_COMMAND = "jake.bat"; + + var os = require("os"); + + exports.get = function() { + return os.platform() === "win32" ? WINDOWS_BUILD_COMMAND : UNIX_BUILD_COMMAND; + }; + +}()); \ No newline at end of file diff --git a/tests/build/config/jshint.conf.js b/tests/build/config/jshint.conf.js new file mode 100644 index 0000000..68f1d81 --- /dev/null +++ b/tests/build/config/jshint.conf.js @@ -0,0 +1,66 @@ +// Copyright (c) 2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. + +// Configuration options for JSHint. Change this to match your preferences. + +(function() { + "use strict"; + + var merge = require("object-merge"); + + var universalOptions = { + bitwise: true, + curly: false, + eqeqeq: true, + forin: true, + immed: true, + latedef: false, + newcap: true, + noarg: true, + noempty: true, + nonew: true, + regexp: true, + undef: true, + strict: true, + globalstrict: true, // "global" stricts are okay when using CommonJS modules + trailing: true + }; + + exports.nodeOptions = merge(universalOptions, { + node: true + }); + + exports.clientOptions = merge(universalOptions, { + browser: true + }); + + var universalGlobals = { + // Mocha + before: false, + after: false, + beforeEach: false, + afterEach: false, + describe: false, + it: false + }; + + exports.nodeGlobals = merge(universalGlobals, { + // Jake + jake: false, + desc: false, + task: false, + directory: false, + complete: false, + fail: false + }); + + exports.clientGlobals = merge(universalGlobals, { + // CommonJS + exports: false, + require: false, + module: false, + + // Browser + console: false + }); + +}()); \ No newline at end of file diff --git a/tests/build/config/karma.conf.js b/tests/build/config/karma.conf.js new file mode 100644 index 0000000..61f8f7c --- /dev/null +++ b/tests/build/config/karma.conf.js @@ -0,0 +1,75 @@ +// Karma configuration +// Quixote-specific configuration starts with "QUIXOTE:" + +(function() { + "use strict"; + + var paths = require("./paths.js"); + + module.exports = function(config) { + config.set({ + + // base path, that will be used to resolve files and exclude + basePath: '../../..', + + // frameworks to use + frameworks: ['mocha', 'commonjs', 'chai'], + + // list of files / patterns to load in the browser + files: [ + 'tests/src/**/*.js', + 'tests/quixote.js', + 'tests/vendor/**/*.js', + // QUIXOTE: Serve the CSS file so we can load it in our tests + // Mark it `included: false` so Karma doesn't load it automatically + { pattern: 'dist/paper.css', included: false } + ], + + // list of files to exclude + exclude: [], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'tests/src/**/*.js': ['commonjs'], + 'tests/quixote.js': ['commonjs'], + 'tests/vendor/**/*.js': ['commonjs'] + }, + + // test results reporter to use + // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' + reporters: ['dots'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + // Start these browsers, currently available: + // - Chrome + // - ChromeCanary + // - Firefox + // - Opera + // - Safari (only Mac) + // - PhantomJS + // - IE (only Windows) + browsers: [], + + // If browser does not capture in given timeout [ms], kill it + captureTimeout: 60000, + + // Continuous Integration mode + // if true, it capture browsers, run tests and exit + singleRun: false + }); + }; + +}()); diff --git a/tests/build/config/paths.js b/tests/build/config/paths.js new file mode 100644 index 0000000..f53f40e --- /dev/null +++ b/tests/build/config/paths.js @@ -0,0 +1,20 @@ +// Copyright (c) 2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. + +// Lists commonly-used directories. They're all relative to the project root. + +(function() { + "use strict"; + + module.exports = { + generatedDir: "generated", + testDir: "generated/test", + distDir: "generated/dist", + clientDistDir: "generated/dist/client", + + buildDir: "build", + clientDir: "src", + clientEntryPoint: "src/toggle.js", + clientDistBundle: "generated/dist/client/bundle.js" + }; + +}()); \ No newline at end of file diff --git a/tests/build/config/tested_browsers.js b/tests/build/config/tested_browsers.js new file mode 100644 index 0000000..61cf10c --- /dev/null +++ b/tests/build/config/tested_browsers.js @@ -0,0 +1,17 @@ +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +(function() { + "use strict"; + + // Uncomment and modify the following list to cause the build to fail unless these browsers are tested. + // There's no Quixote-specific configuration in this file. + + module.exports = [ + //"IE 10.0.0 (Windows 7 0.0.0)", + //"Firefox 41.0.0 (Mac OS X 10.10.0)", + //"Chrome 46.0.2490 (Mac OS X 10.10.5)", + //"Safari 9.0.1 (Mac OS X 10.10.5)", + //"Mobile Safari 8.0.0 (iOS 8.4.0)", + //"Chrome Mobile 44.0.2403 (Android 6.0.0)" + ]; + +}()); \ No newline at end of file diff --git a/tests/build/scripts/build.jakefile.js b/tests/build/scripts/build.jakefile.js new file mode 100644 index 0000000..e371876 --- /dev/null +++ b/tests/build/scripts/build.jakefile.js @@ -0,0 +1,128 @@ +// Copyright (c) 2012-2014 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details. + +// Main build file. Contains all tasks needed for normal development. +// There's no Quixote-specific configuration in this file. + +(function() { + "use strict"; + + var startTime = Date.now(); + + var shell = require("shelljs"); + var jshint = require("simplebuild-jshint"); + var karma = require("simplebuild-karma"); + var browserify = require("../util/browserify_runner.js"); + + var browsers = require("../config/tested_browsers.js"); + var jshintConfig = require("../config/jshint.conf.js"); + var paths = require("../config/paths.js"); + + var KARMA_CONFIG = "./build/config/karma.conf.js"; + + var strict = !process.env.loose; + + + //*** GENERAL + + desc("Lint and test"); + task("default", [ "lint", "test" ], function() { + var elapsedSeconds = (Date.now() - startTime) / 1000; + console.log("\n\nBUILD OK (" + elapsedSeconds.toFixed(2) + "s)"); + }); + + desc("Start server (for manual testing)"); + task("run", [ "build" ], function() { + jake.exec("node ../node_modules/http-server/bin/http-server " + paths.clientDistDir, { interactive: true }, complete); + }, { async: true }); + + desc("Delete generated files"); + task("clean", function() { + shell.rm("-rf", paths.generatedDir); + }); + + + //*** LINT + + desc("Lint everything"); + task("lint", ["lintNode", "lintClient"]); + + task("lintNode", function() { + process.stdout.write("Linting Node.js code: "); + jshint.checkFiles({ + files: [ paths.buildDir + "/**/*.js" ], + options: jshintConfig.nodeOptions, + globals: jshintConfig.nodeGlobals + }, complete, fail); + }, { async: true }); + + task("lintClient", function() { + process.stdout.write("Linting browser code: "); + jshint.checkFiles({ + files: [ paths.clientDir + "/**/*.js" ], + options: jshintConfig.clientOptions, + globals: jshintConfig.clientGlobals + }, complete, fail); + }, { async: true }); + + + //*** TEST + + desc("Start Karma server -- run this first"); + task("karma", function() { + karma.start({ + configFile: KARMA_CONFIG + }, complete, fail); + }, { async: true }); + + desc("Run tests"); + task("test", function() { + console.log("Testing browser code: "); + + var browsersToCapture = process.env.capture ? process.env.capture.split(",") : []; + karma.run({ + configFile: KARMA_CONFIG, + expectedBrowsers: browsers, + strict: strict, + capture: browsersToCapture + }, complete, fail); + }, { async: true }); + + + //*** BUILD + + desc("Build distribution package"); + task("build", [ "prepDistDir", "buildClient" ]); + + task("prepDistDir", function() { + shell.rm("-rf", paths.distDir); + }); + + task("buildClient", [ paths.clientDistDir, "bundleClientJs" ], function() { + console.log("Copying client code: ."); + shell.cp( + paths.clientDir + "/*.html", + paths.clientDir + "/*.css", + paths.clientDir + "/*.svg", + paths.clientDistDir + ); + }); + + task("bundleClientJs", [ paths.clientDistDir ], function() { + console.log("Bundling browser code with Browserify: ."); + browserify.bundle({ + entry: paths.clientEntryPoint, + outfile: paths.clientDistBundle, + options: { + standalone: "toggle", + debug: true + } + }, complete, fail); + }, { async: true }); + + + //*** CREATE DIRECTORIES + + directory(paths.testDir); + directory(paths.clientDistDir); + +}()); \ No newline at end of file diff --git a/tests/build/scripts/run_jake.bat b/tests/build/scripts/run_jake.bat new file mode 100644 index 0000000..edbfe6b --- /dev/null +++ b/tests/build/scripts/run_jake.bat @@ -0,0 +1,7 @@ +@echo off +REM Runs Jake from node_modules directory, preventing it from needing to be installed globally +REM Also ensures node modules have been installed +REM There's no Quixote-specific configuration in this file. + +if not exist node_modules\.bin\jake.cmd call npm install +node_modules\.bin\jake %* \ No newline at end of file diff --git a/tests/build/scripts/run_jake.sh b/tests/build/scripts/run_jake.sh new file mode 100644 index 0000000..89804b6 --- /dev/null +++ b/tests/build/scripts/run_jake.sh @@ -0,0 +1,6 @@ +# Runs Jake from node_modules directory, preventing it from needing to be installed globally +# Also ensures node modules have been installed +# There's no Quixote-specific configuration in this file. + +[ ! -f node_modules/.bin/jake ] && echo "Installing npm modules:" && npm install +node_modules/.bin/jake $* \ No newline at end of file diff --git a/tests/build/scripts/watch.js b/tests/build/scripts/watch.js new file mode 100644 index 0000000..ee4ceb3 --- /dev/null +++ b/tests/build/scripts/watch.js @@ -0,0 +1,28 @@ +// Copyright (c) 2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. + +// Automatically runs build when files change. +// There's no Quixote-specific configuration in this file. + +(function() { + "use strict"; + + var nodemon = require("nodemon"); + var buildCommand = require("../config/build_command.js"); + var paths = require("../config/paths.js"); + + console.log("*** Using nodemon to run " + buildCommand.get() + ". Type 'rs' to force restart."); + nodemon({ + ext: "sh bat json js html css", + ignore: paths.generatedDir, + exec: buildCommand.get() + " " + process.argv.slice(2).join(" "), + execMap: { + sh: "/bin/sh", + bat: "cmd.exe /c", + cmd: "cmd.exe /c" + } + }).on("restart", function(files) { + if (files) console.log("*** Restarting due to", files); + else console.log("*** Restarting"); + }); + +}()); diff --git a/tests/build/util/browserify_runner.js b/tests/build/util/browserify_runner.js new file mode 100644 index 0000000..353d878 --- /dev/null +++ b/tests/build/util/browserify_runner.js @@ -0,0 +1,21 @@ +/* Copyright (c) 2014 Titanium I.T. LLC - See LICENSE.txt for license */ + +// Helper function for running Browserify +// There's no Quixote-specific configuration in this file. + +"use strict"; + +var fs = require("fs"); +var path = require("path"); +var browserify = require("browserify"); + +exports.bundle = function(config, success, failure) { + var b = browserify(config.options); + + b.add(path.resolve(config.entry)); + b.bundle(function(err, bundle) { + if (err) return failure(err); + fs.writeFileSync(config.outfile, bundle); + return success(); + }); +}; \ No newline at end of file diff --git a/tests/build/util/karma_runner.js b/tests/build/util/karma_runner.js new file mode 100644 index 0000000..535b22a --- /dev/null +++ b/tests/build/util/karma_runner.js @@ -0,0 +1,76 @@ +// Copyright (c) 2012-2015 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details. + +// Helper functions for running Karma +// There's no Quixote-specific configuration in this file. + +(function() { + "use strict"; + + var path = require("path"); + var sh = require("./sh.js"); + var runner = require("karma/lib/runner"); + var server = require("karma/lib/server"); + + var KARMA = "node node_modules/karma/bin/karma"; + + exports.serve = function(configFile, success, fail) { + var command = KARMA + " start " + configFile; + sh.run(command, success, function () { + fail("Could not start Karma server"); + }); + }; + + exports.runTests = function(options, success, fail) { + options.capture = options.capture || []; + var config = { + configFile: path.resolve(options.configFile), + browsers: options.capture, + singleRun: options.capture.length > 0 + }; + + var runKarma = runner.run.bind(runner); + if (config.singleRun) runKarma = server.start.bind(server); + + var stdout = new CapturedStdout(); + runKarma(config, function(exitCode) { + stdout.restore(); + + if (exitCode) return fail("Client tests failed (did you start the Karma server?)"); + var browserMissing = checkRequiredBrowsers(options.browsers, stdout); + if (browserMissing && options.strict) return fail("Did not test all browsers"); + if (stdout.capturedOutput.indexOf("TOTAL: 0 SUCCESS") !== -1) return fail("No tests were run!"); + + return success(); + }); + }; + + function checkRequiredBrowsers(requiredBrowsers, stdout) { + var browserMissing = false; + requiredBrowsers.forEach(function(browser) { + browserMissing = lookForBrowser(browser, stdout.capturedOutput) || browserMissing; + }); + return browserMissing; + } + + function lookForBrowser(browser, output) { + var missing = output.indexOf(browser + ": Executed") === -1; + if (missing) console.log("Warning: " + browser + " was not tested!"); + return missing; + } + + function CapturedStdout() { + var self = this; + self.oldStdout = process.stdout.write; + self.capturedOutput = ""; + + process.stdout.write = function(data) { + self.capturedOutput += data; + self.oldStdout.apply(this, arguments); + }; + } + + CapturedStdout.prototype.restore = function() { + process.stdout.write = this.oldStdout; + }; + +}()); \ No newline at end of file diff --git a/tests/build/util/mocha_runner.js b/tests/build/util/mocha_runner.js new file mode 100644 index 0000000..eb8bc86 --- /dev/null +++ b/tests/build/util/mocha_runner.js @@ -0,0 +1,54 @@ +// Copyright (c) 2014-2015 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details. + +// Helper function for running Mocha +// There's no Quixote-specific configuration in this file. + +(function() { + "use strict"; + + var Mocha = require("mocha"); + var jake = require("jake"); + + exports.runTests = function runTests(options, success, failure) { + var mocha = new Mocha(options.options); + var files = deglob(options.files); + files.forEach(mocha.addFile.bind(mocha)); + + // This is a bit of a hack. The issue is this: during test execution, if an exception is thrown inside + // of a callback (and keep in mind that assertions throw exceptions), there's no way for Mocha to catch + // that exception. + // So Mocha registers an 'uncaughtException' handler on Node's process object. That way any unhandled + // exception is passed to Mocha. + // The problem is that Jake ALSO listens for 'uncaughtException'. Its handler and Mocha's handler don't + // get along. Somehow the Jake handler seems to terminate Mocha's test run... not sure why. We need to + // disable Jake's handler while Mocha is running. + // This code disables ALL uncaughtException handlers and then restores them after Mocha is done. It's + // very hacky and likely to cause problems in certain edge cases (for example, '.once' listeners aren't + // restored properly), but it seems to be working for now. + // It might be possible to create a better solution by using Node's 'domain' module. Something to look + // into if you're reading this. Another solution is to just spawn Mocha in a separate process, but I + // didn't want the time penalty involved. Besides, this seems to be working okay. + var savedListeners = disableExceptionListeners(); + + var runner = mocha.run(function(failures) { + restoreExceptionListeners(savedListeners); + if (failures) return failure("Tests failed"); + else return success(); + }); + }; + + function deglob(globs) { + return new jake.FileList(globs).toArray(); + } + + function disableExceptionListeners() { + var listeners = process.listeners("uncaughtException"); + process.removeAllListeners("uncaughtException"); + return listeners; + } + + function restoreExceptionListeners(listeners) { + listeners.forEach(process.addListener.bind(process, "uncaughtException")); + } + +}()); diff --git a/tests/build/util/sh.js b/tests/build/util/sh.js new file mode 100644 index 0000000..78b3881 --- /dev/null +++ b/tests/build/util/sh.js @@ -0,0 +1,48 @@ +// Copyright (c) 2012-2015 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details. + +// Helper functions for running processes. +// There's no Quixote-specific configuration in this file. + +(function() { + "use strict"; + + var jake = require("jake"); + + exports.runMany = function(commands, successCallback, failureCallback) { + var stdout = []; + function serializedSh(command) { + if (command) { + run(command, function(oneStdout) { + stdout.push(oneStdout); + serializedSh(commands.shift()); + }, failureCallback); + } + else { + successCallback(stdout); + } + } + serializedSh(commands.shift()); + }; + + var run = exports.run = function(oneCommand, successCallback, failureCallback) { + var stdout = ""; + var child = jake.createExec(oneCommand); + child.on("stdout", function(data) { + process.stdout.write(data); + stdout += data; + }); + child.on("stderr", function(data) { + process.stderr.write(data); + }); + child.on("cmdEnd", function() { + successCallback(stdout); + }); + child.on("error", function() { + failureCallback(stdout); + }); + + console.log("> " + oneCommand); + child.run(); + }; + +}()); \ No newline at end of file diff --git a/tests/build/util/version_checker.js b/tests/build/util/version_checker.js new file mode 100644 index 0000000..f4bbd1e --- /dev/null +++ b/tests/build/util/version_checker.js @@ -0,0 +1,29 @@ +// Copyright (c) 2013 Titanium I.T. LLC. All rights reserved. See LICENSE.TXT for details. + +// Helper function for checking version numbers. +// There's no Quixote-specific configuration in this file. + + +(function() { + "use strict"; + + var semver = require("semver"); + + exports.check = function(options, success, fail) { + if (options.strict) { + if (semver.neq(options.actual, options.expected)) return failWithQualifier("exactly"); + } + else { + if (semver.lt(options.actual, options.expected)) return failWithQualifier("at least"); + if (semver.neq(options.actual, options.expected)) console.log("Warning: Newer " + options.name + + " version than expected. Expected " + options.expected + ", but was " + options.actual + "."); + } + return success(); + + function failWithQualifier(qualifier) { + return fail("Incorrect " + options.name + " version. Expected " + qualifier + + " " + options.expected + ", but was " + options.actual + "."); + } + }; + +}()); \ No newline at end of file diff --git a/tests/jake.bat b/tests/jake.bat new file mode 100644 index 0000000..774c0fc --- /dev/null +++ b/tests/jake.bat @@ -0,0 +1 @@ +@call build\scripts\run_jake -f build\scripts\build.jakefile.js %* diff --git a/tests/jake.sh b/tests/jake.sh new file mode 100644 index 0000000..a3bff26 --- /dev/null +++ b/tests/jake.sh @@ -0,0 +1,2 @@ +#!/bin/sh +. build/scripts/run_jake.sh -f build/scripts/build.jakefile.js $* diff --git a/tests/package.json b/tests/package.json new file mode 100644 index 0000000..1055f55 --- /dev/null +++ b/tests/package.json @@ -0,0 +1,33 @@ +{ + "name": "papercss-tests", + "version": "1.1.0", + "description": "Another CSS framework", + "dependencies": { + "http-server": "^0.9.0" + }, + "devDependencies": { + "browserify": "^14.1.0", + "chai": "^4.1.2", + "jake": "^8.0.15", + "jshint": "^2.9.4", + "karma": "^1.7.1", + "karma-chai": "^0.1.0", + "karma-commonjs": "1.0.0", + "karma-firefox-launcher": "^1.0.0", + "karma-mocha": "^1.3.0", + "karma-quixote": "^1.0.0", + "less": "2.7.2", + "mocha": "^4.0.1", + "nodemon": "^1.11.0", + "normalize.css": "7.0.0", + "npm": "5.3.0", + "object-merge": "^2.5.1", + "procfile": "^0.1.1", + "quixote": "^0.14.0", + "semver": "^5.3.0", + "shelljs": "^0.7.6", + "simplebuild-jshint": "^1.3.0", + "simplebuild-karma": "^1.0.0", + "webpack-dev-server": "2.7.1" + } +} diff --git a/tests/quixote.js b/tests/quixote.js new file mode 100644 index 0000000..bd9f804 --- /dev/null +++ b/tests/quixote.js @@ -0,0 +1,4195 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.quixote = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],2:[function(require,module,exports){ +// Copyright (c) 2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("./util/ensure.js"); +var oop = require("./util/oop.js"); +var shim = require("./util/shim.js"); + +var Me = module.exports = function Assertable() { + ensure.unreachable("Assertable is abstract and should not be constructed directly."); +}; +Me.extend = oop.extendFn(Me); +oop.makeAbstract(Me, []); + +Me.prototype.assert = function assert(expected, message) { + ensure.signature(arguments, [ Object, [undefined, String] ]); + if (message === undefined) message = "Differences found"; + + var diff = this.diff(expected); + if (diff !== "") throw new Error(message + ":\n" + diff + "\n"); +}; + +Me.prototype.diff = function diff(expected) { + ensure.signature(arguments, [ Object ]); + + var result = []; + var keys = shim.Object.keys(expected); + var key, oneDiff, descriptor; + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + descriptor = this[key]; + ensure.that( + descriptor !== undefined, + this + " doesn't have a property named '" + key + "'. Did you misspell it?" + ); + oneDiff = descriptor.diff(expected[key]); + if (oneDiff !== "") result.push(oneDiff); + } + + return result.join("\n"); +}; + +},{"./util/ensure.js":23,"./util/oop.js":24,"./util/shim.js":25}],3:[function(require,module,exports){ +// Copyright (c) 2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var PositionDescriptor = require("./position_descriptor.js"); +var Position = require("../values/position.js"); + +var X_DIMENSION = "x"; +var Y_DIMENSION = "y"; + +var Me = module.exports = function AbsolutePosition(dimension, value) { + ensure.signature(arguments, [ String, Number ]); + + switch(dimension) { + case X_DIMENSION: + PositionDescriptor.x(this); + this._value = Position.x(value); + break; + case Y_DIMENSION: + PositionDescriptor.y(this); + this._value = Position.y(value); + break; + default: ensure.unreachable("Unknown dimension: " + dimension); + } + this._dimension = dimension; +}; +PositionDescriptor.extend(Me); + +Me.x = function(value) { + ensure.signature(arguments, [ Number ]); + return new Me(X_DIMENSION, value); +}; + +Me.y = function(value) { + ensure.signature(arguments, [ Number ]); + return new Me(Y_DIMENSION, value); +}; + +Me.prototype.value = function() { + return this._value; +}; + +Me.prototype.toString = function() { + return this._value + " " + this._dimension + "-coordinate"; +}; + +},{"../util/ensure.js":23,"../values/position.js":27,"./position_descriptor.js":11}],4:[function(require,module,exports){ +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var PositionDescriptor = require("./position_descriptor.js"); +var Position = require("../values/position.js"); +var RelativePosition = require("./relative_position.js"); + +var X_DIMENSION = "x"; +var Y_DIMENSION = "y"; + +var Me = module.exports = function Center(dimension, position1, position2, description) { + ensure.signature(arguments, [ String, PositionDescriptor, PositionDescriptor, String ]); + + if (dimension === X_DIMENSION) PositionDescriptor.x(this); + else if (dimension === Y_DIMENSION) PositionDescriptor.y(this); + else ensure.unreachable("Unknown dimension: " + dimension); + + this._dimension = dimension; + this._position1 = position1; + this._position2 = position2; + this._description = description; +}; +PositionDescriptor.extend(Me); + +Me.x = factoryFn(X_DIMENSION); +Me.y = factoryFn(Y_DIMENSION); + +Me.prototype.value = function value() { + ensure.signature(arguments, []); + return this._position1.value().midpoint(this._position2.value()); +}; + +Me.prototype.toString = function toString() { + ensure.signature(arguments, []); + return this._description; +}; + +function factoryFn(dimension) { + return function(position1, position2, description) { + return new Me(dimension, position1, position2, description); + }; +} + +},{"../util/ensure.js":23,"../values/position.js":27,"./position_descriptor.js":11,"./relative_position.js":12}],5:[function(require,module,exports){ +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var oop = require("../util/oop.js"); +var Value = require("../values/value.js"); + +var Me = module.exports = function Descriptor() { + ensure.unreachable("Descriptor is abstract and should not be constructed directly."); +}; +Me.extend = oop.extendFn(Me); +oop.makeAbstract(Me, [ + "value", + "toString" +]); + +Me.prototype.diff = function diff(expected) { + expected = normalizeType(this, expected); + try { + var actualValue = this.value(); + var expectedValue = expected.value(); + + if (actualValue.equals(expectedValue)) return ""; + + var difference = actualValue.diff(expectedValue); + var expectedDesc = expectedValue.toString(); + if (expected instanceof Me) expectedDesc += " (" + expected + ")"; + + return this + " was " + difference + ".\n" + + " Expected: " + expectedDesc + "\n" + + " But was: " + actualValue; + } + catch (err) { + throw new Error("Can't compare " + this + " to " + expected + ": " + err.message); + } +}; + +Me.prototype.convert = function convert(arg, type) { + // This method is meant to be overridden by subclasses. It should return 'undefined' when an argument + // can't be converted. In this default implementation, no arguments can be converted, so we always + // return 'undefined'. + return undefined; +}; + +Me.prototype.equals = function equals(that) { + // Descriptors aren't value objects. They're never equal to anything. But sometimes + // they're used in the same places value objects are used, and this method gets called. + return false; +}; + +function normalizeType(self, expected) { + var expectedType = typeof expected; + if (expected === null) expectedType = "null"; + + if (expectedType === "object" && (expected instanceof Me || expected instanceof Value)) return expected; + + if (expected === undefined) { + throw new Error("Can't compare " + self + " to " + expected + ". Did you misspell a property name?"); + } + else if (expectedType === "object") { + throw new Error("Can't compare " + self + " to " + oop.instanceName(expected) + " instances."); + } + else { + var converted = self.convert(expected, expectedType); + if (converted !== undefined) return converted; + + var explanation = expected; + if (expectedType === "string") explanation = "'" + explanation + "'"; + if (expectedType === "function") explanation = "a function"; + + throw new Error("Can't compare " + self + " to " + explanation + "."); + } + +} + +},{"../util/ensure.js":23,"../util/oop.js":24,"../values/value.js":30}],6:[function(require,module,exports){ +// Copyright (c) 2014-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var Position = require("../values/position.js"); +var PositionDescriptor = require("./position_descriptor.js"); + +var TOP = "top"; +var RIGHT = "right"; +var BOTTOM = "bottom"; +var LEFT = "left"; + +var Me = module.exports = function ElementEdge(element, position) { + var QElement = require("../q_element.js"); // break circular dependency + ensure.signature(arguments, [QElement, String]); + + if (position === LEFT || position === RIGHT) PositionDescriptor.x(this); + else if (position === TOP || position === BOTTOM) PositionDescriptor.y(this); + else ensure.unreachable("Unknown position: " + position); + + this._element = element; + this._position = position; +}; +PositionDescriptor.extend(Me); + +Me.top = factoryFn(TOP); +Me.right = factoryFn(RIGHT); +Me.bottom = factoryFn(BOTTOM); +Me.left = factoryFn(LEFT); + +Me.prototype.value = function value() { + ensure.signature(arguments, []); + + var rawPosition = this._element.getRawPosition(); + + var edge = rawPosition[this._position]; + var scroll = this._element.frame.getRawScrollPosition(); + + if (this._position === RIGHT || this._position === LEFT) { + if (!elementRendered(this, rawPosition)) return Position.noX(); + return Position.x(edge + scroll.x); + } + else { + if (!elementRendered(this, rawPosition)) return Position.noY(); + return Position.y(edge + scroll.y); + } +}; + +Me.prototype.toString = function toString() { + ensure.signature(arguments, []); + return this._position + " edge of " + this._element; +}; + +function factoryFn(position) { + return function factory(element) { + return new Me(element, position); + }; +} + +function elementRendered(self, rawPosition) { + var element = self._element; + + var inDom = element.frame.body().toDomElement().contains(element.toDomElement()); + var displayNone = element.getRawStyle("display") === "none"; + + return inDom && !displayNone; +} + +},{"../q_element.js":17,"../util/ensure.js":23,"../values/position.js":27,"./position_descriptor.js":11}],7:[function(require,module,exports){ +// Copyright (c) 2016-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var RenderState = require("../values/render_state.js"); +var Position = require("../values/position.js"); +var Descriptor = require("./descriptor.js"); +var ElementRenderedEdge = require("./element_rendered_edge.js"); +var GenericSize = require("./generic_size.js"); +var Center = require("./center.js"); + +var Me = module.exports = function ElementRendered(element) { + var QElement = require("../q_element.js"); // break circular dependency + ensure.signature(arguments, [ QElement ]); + + this._element = element; + + // properties + this.top = ElementRenderedEdge.top(element); + this.right = ElementRenderedEdge.right(element); + this.bottom = ElementRenderedEdge.bottom(element); + this.left = ElementRenderedEdge.left(element); + + this.width = GenericSize.create(this.left, this.right, "rendered width of " + element); + this.height = GenericSize.create(this.top, this.bottom, "rendered height of " + element); + + this.center = Center.x(this.left, this.right, "rendered center of " + element); + this.middle = Center.y(this.top, this.bottom, "rendered middle of " + element); +}; +Descriptor.extend(Me); + +Me.create = function create(element) { + return new Me(element); +}; + +Me.prototype.value = function value() { + if (this.top.value().equals(Position.noY())) return RenderState.notRendered(); + else return RenderState.rendered(); +}; + +Me.prototype.toString = function toString() { + return "render status of " + this._element.toString(); +}; + +Me.prototype.convert = function convert(arg, type) { + if (type === "boolean") { + return arg ? RenderState.rendered() : RenderState.notRendered(); + } +}; + +},{"../q_element.js":17,"../util/ensure.js":23,"../values/position.js":27,"../values/render_state.js":28,"./center.js":4,"./descriptor.js":5,"./element_rendered_edge.js":8,"./generic_size.js":9}],8:[function(require,module,exports){ +// Copyright (c) 2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var quixote = require("../quixote.js"); +var PositionDescriptor = require("./position_descriptor.js"); +var Position = require("../values/position.js"); +var Size = require("../values/size.js"); +var RenderState = require("../values/render_state.js"); + +var TOP = "top"; +var RIGHT = "right"; +var BOTTOM = "bottom"; +var LEFT = "left"; + +var Me = module.exports = function ElementVisibleEdge(element, position) { + var QElement = require("../q_element.js"); // break circular dependency + ensure.signature(arguments, [ QElement, String ]); + + if (position === LEFT || position === RIGHT) PositionDescriptor.x(this); + else if (position === TOP || position === BOTTOM) PositionDescriptor.y(this); + else unknownPosition(position); + + this._element = element; + this._position = position; +}; +PositionDescriptor.extend(Me); + +Me.top = factoryFn(TOP); +Me.right = factoryFn(RIGHT); +Me.bottom = factoryFn(BOTTOM); +Me.left = factoryFn(LEFT); + +function factoryFn(position) { + return function factory(element) { + return new Me(element, position); + }; +} + +Me.prototype.toString = function toString() { + ensure.signature(arguments, []); + return this._position + " rendered edge of " + this._element; +}; + +Me.prototype.value = function() { + var position = this._position; + var element = this._element; + var page = element.frame.page(); + + if (element.top.value().equals(Position.noY())) return notRendered(position); + if (element.width.value().equals(Size.create(0))) return notRendered(position); + if (element.height.value().equals(Size.create(0))) return notRendered(position); + + ensure.that( + !hasClipPathProperty(element), + "Can't detect element clipping boundaries when 'clip-path' property is used." + ); + + var bounds = { + top: page.top.value(), + right: null, + bottom: null, + left: page.left.value() + }; + + bounds = intersectionWithOverflow(element, bounds); + bounds = intersectionWithClip(element, bounds); + + var edges = intersection( + bounds, + element.top.value(), + element.right.value(), + element.bottom.value(), + element.left.value() + ); + + if (isClippedOutOfExistence(bounds, edges)) return notRendered(position); + else return edge(edges, position); +}; + +function hasClipPathProperty(element) { + var clipPath = element.getRawStyle("clip-path"); + return clipPath !== "none" && clipPath !== ""; +} + +function intersectionWithOverflow(element, bounds) { + for (var container = element.parent(); container !== null; container = container.parent()) { + if (isClippedByAncestorOverflow(element, container)) { + bounds = intersection( + bounds, + container.top.value(), + container.right.value(), + container.bottom.value(), + container.left.value() + ); + } + } + + return bounds; +} + +function intersectionWithClip(element, bounds) { + // WORKAROUND IE 8: Doesn't have any way to detect 'clip: auto' value. + ensure.that(!quixote.browser.misreportsClipAutoProperty(), + "Can't determine element clipping values on this browser because it misreports the value of the" + + " `clip: auto` property. You can use `quixote.browser.misreportsClipAutoProperty()` to skip this browser." + ); + + for ( ; element !== null; element = element.parent()) { + var clip = element.getRawStyle("clip"); + if (clip === "auto" || !canBeClippedByClipProperty(element)) continue; + + var clipEdges = normalizeClipProperty(element, clip); + bounds = intersection( + bounds, + clipEdges.top, + clipEdges.right, + clipEdges.bottom, + clipEdges.left + ); + } + + return bounds; +} + +function normalizeClipProperty(element, clip) { + var clipValues = parseClipProperty(element, clip); + + return { + top: clipValues[0] === "auto" ? + element.top.value() : + element.top.value().plus(Position.y(Number(clipValues[0]))), + right: clipValues[1] === "auto" ? + element.right.value() : + element.left.value().plus(Position.x(Number(clipValues[1]))), + bottom: clipValues[2] === "auto" ? + element.bottom.value() : + element.top.value().plus(Position.y(Number(clipValues[2]))), + left: clipValues[3] === "auto" ? + element.left.value() : + element.left.value().plus(Position.x(Number(clipValues[3]))) + }; + + function parseClipProperty(element, clip) { + // WORKAROUND IE 11, Chrome Mobile 44: Reports 0px instead of 'auto' when computing rect() in clip property. + ensure.that(!quixote.browser.misreportsAutoValuesInClipProperty(), + "Can't determine element clipping values on this browser because it misreports the value of the `clip`" + + " property. You can use `quixote.browser.misreportsAutoValuesInClipProperty()` to skip this browser." + ); + + var clipRegex = /rect\((.*?),? (.*?),? (.*?),? (.*?)\)/; + var matches = clipRegex.exec(clip); + ensure.that(matches !== null, "Unable to parse clip property: " + clip); + + return [ + parseLength(matches[1], clip), + parseLength(matches[2], clip), + parseLength(matches[3], clip), + parseLength(matches[4], clip) + ]; + } + + function parseLength(pxString, clip) { + if (pxString === "auto") return pxString; + + var pxRegex = /^(.*?)px$/; + var matches = pxRegex.exec(pxString); + ensure.that(matches !== null, "Unable to parse '" + pxString + "' in clip property: " + clip); + + return matches[1]; + } +} + +function isClippedByAncestorOverflow(element, ancestor) { + return canBeClippedByOverflowProperty(element) && hasClippingOverflow(ancestor); +} + +function canBeClippedByOverflowProperty(element) { + var position = element.getRawStyle("position"); + switch (position) { + case "static": + case "relative": + case "absolute": + case "sticky": + return true; + case "fixed": + return false; + default: + ensure.unreachable("Unknown position property: " + position); + } +} + +function hasClippingOverflow(element) { + var overflow = element.getRawStyle("overflow"); + switch (overflow) { + case "hidden": + case "scroll": + case "auto": + return true; + case "visible": + return false; + default: + ensure.unreachable("Unknown overflow property: " + overflow); + } +} + +function canBeClippedByClipProperty(element) { + var position = element.getRawStyle("position"); + switch (position) { + case "absolute": + case "fixed": + return true; + case "static": + case "relative": + case "sticky": + return false; + default: + ensure.unreachable("Unknown position property: " + position); + } +} + +function intersection(bounds, top, right, bottom, left) { + bounds.top = bounds.top.max(top); + bounds.right = (bounds.right === null) ? right : bounds.right.min(right); + bounds.bottom = (bounds.bottom === null) ? bottom : bounds.bottom.min(bottom); + bounds.left = bounds.left.max(left); + + return bounds; +} + +function isClippedOutOfExistence(bounds, edges) { + return (bounds.top.compare(edges.bottom) >= 0) || + (bounds.right !== null && bounds.right.compare(edges.left) <= 0) || + (bounds.bottom !== null && bounds.bottom.compare(edges.top) <= 0) || + (bounds.left.compare(edges.right) >= 0); +} + +function notRendered(position) { + switch(position) { + case TOP: + case BOTTOM: + return Position.noY(); + case LEFT: + case RIGHT: + return Position.noX(); + default: unknownPosition(position); + } +} + +function edge(edges, position) { + switch(position) { + case TOP: return edges.top; + case RIGHT: return edges.right; + case BOTTOM: return edges.bottom; + case LEFT: return edges.left; + default: unknownPosition(position); + } +} + +function unknownPosition(position) { + ensure.unreachable("Unknown position: " + position); +} + +},{"../q_element.js":17,"../quixote.js":22,"../util/ensure.js":23,"../values/position.js":27,"../values/render_state.js":28,"../values/size.js":29,"./position_descriptor.js":11}],9:[function(require,module,exports){ +// Copyright (c) 2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var PositionDescriptor = require("./position_descriptor.js"); +var SizeDescriptor = require("./size_descriptor.js"); + +var Me = module.exports = function GenericSize(from, to, description) { + ensure.signature(arguments, [ PositionDescriptor, PositionDescriptor, String ]); + + this._from = from; + this._to = to; + this._description = description; +}; +SizeDescriptor.extend(Me); + +Me.create = function(from, to, description) { + return new Me(from, to, description); +}; + +Me.prototype.value = function() { + ensure.signature(arguments, []); + return this._from.value().distanceTo(this._to.value()); +}; + +Me.prototype.toString = function() { + return this._description; +}; + +},{"../util/ensure.js":23,"./position_descriptor.js":11,"./size_descriptor.js":14}],10:[function(require,module,exports){ +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var PositionDescriptor = require("./position_descriptor.js"); +var Position = require("../values/position.js"); + +var TOP = "top"; +var RIGHT = "right"; +var BOTTOM = "bottom"; +var LEFT = "left"; + +var Me = module.exports = function PageEdge(edge, frame) { + var QFrame = require("../q_frame.js"); // break circular dependency + ensure.signature(arguments, [ String, QFrame ]); + + if (edge === LEFT || edge === RIGHT) PositionDescriptor.x(this); + else if (edge === TOP || edge === BOTTOM) PositionDescriptor.y(this); + else ensure.unreachable("Unknown edge: " + edge); + + this._edge = edge; + this._frame = frame; +}; +PositionDescriptor.extend(Me); + +Me.top = factoryFn(TOP); +Me.right = factoryFn(RIGHT); +Me.bottom = factoryFn(BOTTOM); +Me.left = factoryFn(LEFT); + +Me.prototype.value = function value() { + ensure.signature(arguments, []); + + var size = pageSize(this._frame.toDomElement().contentDocument); + switch(this._edge) { + case TOP: return Position.y(0); + case RIGHT: return Position.x(size.width); + case BOTTOM: return Position.y(size.height); + case LEFT: return Position.x(0); + + default: ensure.unreachable(); + } +}; + +Me.prototype.toString = function toString() { + ensure.signature(arguments, []); + + switch(this._edge) { + case TOP: return "top of page"; + case RIGHT: return "right side of page"; + case BOTTOM: return "bottom of page"; + case LEFT: return "left side of page"; + + default: ensure.unreachable(); + } +}; + +function factoryFn(edge) { + return function factory(frame) { + return new Me(edge, frame); + }; +} + + + +// USEFUL READING: http://www.quirksmode.org/mobile/viewports.html +// and http://www.quirksmode.org/mobile/viewports2.html + +// API SEMANTICS. +// Ref https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/Determining_the_dimensions_of_elements +// getBoundingClientRect().width: sum of bounding boxes of element (the displayed width of the element, +// including padding and border). Fractional. Applies transformations. +// clientWidth: visible width of element including padding (but not border). EXCEPT on root element (html), where +// it is the width of the viewport. Rounds to an integer. Doesn't apply transformations. +// offsetWidth: visible width of element including padding, border, and scrollbars (if any). Rounds to an integer. +// Doesn't apply transformations. +// scrollWidth: entire width of element, including any part that's not visible due to scrollbars. Rounds to +// an integer. Doesn't apply transformations. Not clear if it includes scrollbars, but I think not. Also +// not clear if it includes borders or padding. (But from tests, apparently not borders. Except on root +// element and body element, which have special results that vary by browser.) + +// TEST RESULTS: WIDTH +// ✔ = correct answer +// ✘ = incorrect answer and diverges from spec +// ~ = incorrect answer, but matches spec +// BROWSERS TESTED: Safari 6.2.0 (Mac OS X 10.8.5); Mobile Safari 7.0.0 (iOS 7.1); Firefox 32.0.0 (Mac OS X 10.8); +// Firefox 33.0.0 (Windows 7); Chrome 38.0.2125 (Mac OS X 10.8.5); Chrome 38.0.2125 (Windows 7); IE 8, 9, 10, 11 + +// html width style smaller than viewport width; body width style smaller than html width style +// NOTE: These tests were conducted when correct result was width of border. That has been changed +// to "width of viewport." +// html.getBoundingClientRect().width +// ✘ IE 8, 9, 10: width of viewport +// ✔ Safari, Mobile Safari, Chrome, Firefox, IE 11: width of html, including border +// html.clientWidth +// ~ Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11: width of viewport +// html.offsetWidth +// ✘ IE 8, 9, 10: width of viewport +// ✔ Safari, Mobile Safari, Chrome, Firefox, IE 11: width of html, including border +// html.scrollWidth +// ✘ IE 8, 9, 10, 11, Firefox: width of viewport +// ~ Safari, Mobile Safari, Chrome: width of html, excluding border +// body.getBoundingClientRect().width +// ~ Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11: width of body, including border +// body.clientWidth +// ~ Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11: width of body, excluding border +// body.offsetWidth +// ~ Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11: width of body, including border +// body.scrollWidth +// ✘ Safari, Mobile Safari, Chrome: width of viewport +// ~ Firefox, IE 8, 9, 10, 11: width of body, excluding border + +// element width style wider than viewport; body and html width styles at default +// BROWSER BEHAVIOR: html and body border extend to width of viewport and not beyond (except on Mobile Safari) +// Correct result is element width + body border-left + html border-left (except on Mobile Safari) +// Mobile Safari uses a layout viewport, so it's expected to include body border-right and html border-right. +// html.getBoundingClientRect().width +// ✔ Mobile Safari: element width + body border + html border +// ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width +// html.clientWidth +// ✔ Mobile Safari: element width + body border + html border +// ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width +// html.offsetWidth +// ✔ Mobile Safari: element width + body border + html border +// ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width +// html.scrollWidth +// ✔ Mobile Safari: element width + body border + html border +// ✘ Safari, Chrome: element width + body border-left (BUT NOT html border-left) +// ✔ Firefox, IE 8, 9, 10, 11: element width + body border-left + html border-left +// body.getBoundingClientRect().width +// ~ Mobile Safari: element width + body border +// ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width - html border +// body.clientWidth +// ~ Mobile Safari: element width +// ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width - html border - body border +// body.offsetWidth +// ~ Mobile Safari: element width + body border +// ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width - html border +// body.scrollWidth +// ✔ Mobile Safari: element width + body border + html border +// ✔ Safari, Chrome: element width + body border-left + html border-left (matches actual browser) +// ~ Firefox, IE 8, 9, 10, 11: element width + +// TEST RESULTS: HEIGHT +// ✔ = correct answer +// ✘ = incorrect answer and diverges from spec +// ~ = incorrect answer, but matches spec + +// html height style smaller than viewport height; body height style smaller than html height style +// NOTE: These tests were conducted when correct result was height of viewport. +// html.clientHeight +// ✔ Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11: height of viewport + +// element height style taller than viewport; body and html width styles at default +// BROWSER BEHAVIOR: html and body border enclose entire element +// Correct result is element width + body border-top + html border-top + body border-bottom + html border-bottom +// html.clientHeight +// ✔ Mobile Safari: element height + all borders +// ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: height of viewport +// html.scrollHeight +// ✔ Firefox, IE 8, 9, 10, 11: element height + all borders +// ✘ Safari, Mobile Safari, Chrome: element height + html border-bottom +// body.scrollHeight +// ✔ Safari, Mobile Safari, Chrome: element height + all borders +// ~ Firefox, IE 8, 9, 10, 11: element height (body height - body border) +function pageSize(document) { + var html = document.documentElement; + var body = document.body; + +// BEST WIDTH ANSWER SO FAR (ASSUMING VIEWPORT IS MINIMUM ANSWER): + var width = Math.max(body.scrollWidth, html.scrollWidth); + +// BEST HEIGHT ANSWER SO FAR (ASSUMING VIEWPORT IS MINIMUM ANSWER): + var height = Math.max(body.scrollHeight, html.scrollHeight); + + return { + width: width, + height: height + }; +} + +},{"../q_frame.js":19,"../util/ensure.js":23,"../values/position.js":27,"./position_descriptor.js":11}],11:[function(require,module,exports){ +// Copyright (c) 2014-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +/*eslint new-cap: "off" */ +"use strict"; + +var ensure = require("../util/ensure.js"); +var oop = require("../util/oop.js"); +var Descriptor = require("./descriptor.js"); +var Position = require("../values/position.js"); + +// break circular dependencies +function RelativePosition() { + return require("./relative_position.js"); +} +function AbsolutePosition() { + return require("./absolute_position.js"); +} +function GenericSize() { + return require("./generic_size.js"); +} + +var X_DIMENSION = "x"; +var Y_DIMENSION = "y"; + +var Me = module.exports = function PositionDescriptor(dimension) { + ensure.signature(arguments, [ String ]); + ensure.unreachable("PositionDescriptor is abstract and should not be constructed directly."); +}; +Descriptor.extend(Me); +Me.extend = oop.extendFn(Me); + +Me.x = factoryFn(X_DIMENSION); +Me.y = factoryFn(Y_DIMENSION); + +Me.prototype.plus = function plus(amount) { + if (this._pdbc.dimension === X_DIMENSION) return RelativePosition().right(this, amount); + else return RelativePosition().down(this, amount); +}; + +Me.prototype.minus = function minus(amount) { + if (this._pdbc.dimension === X_DIMENSION) return RelativePosition().left(this, amount); + else return RelativePosition().up(this, amount); +}; + +Me.prototype.to = function to(position) { + ensure.signature(arguments, [[ Me, Number ]]); + if (typeof position === "number") { + if (this._pdbc.dimension === X_DIMENSION) position = AbsolutePosition().x(position); + else position = AbsolutePosition().y(position); + } + if (this._pdbc.dimension !== position._pdbc.dimension) { + throw new Error("Can only calculate distance between two X coordinates or two Y coordinates"); + } + + return GenericSize().create(this, position, "distance from " + this + " to " + position); +}; + +Me.prototype.convert = function convert(arg, type) { + switch (type) { + case "number": return this._pdbc.dimension === X_DIMENSION ? Position.x(arg) : Position.y(arg); + case "string": + if (arg === "none") return this._pdbc.dimension === X_DIMENSION ? Position.noX() : Position.noY(); + else return undefined; + break; + default: return undefined; + } +}; + +function factoryFn(dimension) { + return function factory(self) { + // _pdbc: "PositionDescriptor base class." An attempt to prevent name conflicts. + self._pdbc = { dimension: dimension }; + }; +} + +},{"../util/ensure.js":23,"../util/oop.js":24,"../values/position.js":27,"./absolute_position.js":3,"./descriptor.js":5,"./generic_size.js":9,"./relative_position.js":12}],12:[function(require,module,exports){ +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var Descriptor = require("./descriptor.js"); +var PositionDescriptor = require("./position_descriptor.js"); +var Value = require("../values/value.js"); +var Size = require("../values/size.js"); + +var X_DIMENSION = "x"; +var Y_DIMENSION = "y"; +var PLUS = 1; +var MINUS = -1; + +var Me = module.exports = function RelativePosition(dimension, direction, relativeTo, relativeAmount) { + ensure.signature(arguments, [ String, Number, Descriptor, [Number, Descriptor, Value] ]); + + if (dimension === X_DIMENSION) PositionDescriptor.x(this); + else if (dimension === Y_DIMENSION) PositionDescriptor.y(this); + else ensure.unreachable("Unknown dimension: " + dimension); + + this._dimension = dimension; + this._direction = direction; + this._relativeTo = relativeTo; + + if (typeof relativeAmount === "number") { + if (relativeAmount < 0) this._direction *= -1; + this._amount = Size.create(Math.abs(relativeAmount)); + } + else { + this._amount = relativeAmount; + } +}; +PositionDescriptor.extend(Me); + +Me.right = createFn(X_DIMENSION, PLUS); +Me.down = createFn(Y_DIMENSION, PLUS); +Me.left = createFn(X_DIMENSION, MINUS); +Me.up = createFn(Y_DIMENSION, MINUS); + +function createFn(dimension, direction) { + return function create(relativeTo, relativeAmount) { + return new Me(dimension, direction, relativeTo, relativeAmount); + }; +} + +Me.prototype.value = function value() { + ensure.signature(arguments, []); + + var baseValue = this._relativeTo.value(); + var relativeValue = this._amount.value(); + + if (this._direction === PLUS) return baseValue.plus(relativeValue); + else return baseValue.minus(relativeValue); +}; + +Me.prototype.toString = function toString() { + ensure.signature(arguments, []); + + var base = this._relativeTo.toString(); + if (this._amount.equals(Size.create(0))) return base; + + var relation = this._amount.toString(); + if (this._dimension === X_DIMENSION) relation += (this._direction === PLUS) ? " to right of " : " to left of "; + else relation += (this._direction === PLUS) ? " below " : " above "; + + return relation + base; +}; + +},{"../util/ensure.js":23,"../values/size.js":29,"../values/value.js":30,"./descriptor.js":5,"./position_descriptor.js":11}],13:[function(require,module,exports){ +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var Size = require("../values/size.js"); +var Descriptor = require("./descriptor.js"); +var SizeDescriptor = require("./size_descriptor.js"); +var Value = require("../values/value.js"); +var SizeMultiple = require("./size_multiple.js"); + +var PLUS = 1; +var MINUS = -1; + +var Me = module.exports = function RelativeSize(direction, relativeTo, amount) { + ensure.signature(arguments, [ Number, Descriptor, [Number, Descriptor, Value] ]); + + this._direction = direction; + this._relativeTo = relativeTo; + + if (typeof amount === "number") { + this._amount = Size.create(Math.abs(amount)); + if (amount < 0) this._direction *= -1; + } + else { + this._amount = amount; + } +}; +SizeDescriptor.extend(Me); + +Me.larger = factoryFn(PLUS); +Me.smaller = factoryFn(MINUS); + +Me.prototype.value = function value() { + ensure.signature(arguments, []); + + var baseValue = this._relativeTo.value(); + var relativeValue = this._amount.value(); + + if (this._direction === PLUS) return baseValue.plus(relativeValue); + else return baseValue.minus(relativeValue); +}; + +Me.prototype.toString = function toString() { + ensure.signature(arguments, []); + + var base = this._relativeTo.toString(); + if (this._amount.equals(Size.create(0))) return base; + + var relation = this._amount.toString(); + if (this._direction === PLUS) relation += " larger than "; + else relation += " smaller than "; + + return relation + base; +}; + +function factoryFn(direction) { + return function factory(relativeTo, amount) { + return new Me(direction, relativeTo, amount); + }; +} +},{"../util/ensure.js":23,"../values/size.js":29,"../values/value.js":30,"./descriptor.js":5,"./size_descriptor.js":14,"./size_multiple.js":15}],14:[function(require,module,exports){ +// Copyright (c) 2014-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +/*eslint new-cap: "off" */ +"use strict"; + +var ensure = require("../util/ensure.js"); +var oop = require("../util/oop.js"); +var Descriptor = require("./descriptor.js"); +var Size = require("../values/size.js"); + +function RelativeSize() { + return require("./relative_size.js"); // break circular dependency +} + +function SizeMultiple() { + return require("./size_multiple.js"); // break circular dependency +} + +var Me = module.exports = function SizeDescriptor() { + ensure.unreachable("SizeDescriptor is abstract and should not be constructed directly."); +}; +Descriptor.extend(Me); +Me.extend = oop.extendFn(Me); + +Me.prototype.plus = function plus(amount) { + return RelativeSize().larger(this, amount); +}; + +Me.prototype.minus = function minus(amount) { + return RelativeSize().smaller(this, amount); +}; + +Me.prototype.times = function times(amount) { + return SizeMultiple().create(this, amount); +}; + +Me.prototype.convert = function convert(arg, type) { + switch(type) { + case "number": return Size.create(arg); + case "string": return arg === "none" ? Size.createNone() : undefined; + default: return undefined; + } +}; + +},{"../util/ensure.js":23,"../util/oop.js":24,"../values/size.js":29,"./descriptor.js":5,"./relative_size.js":13,"./size_multiple.js":15}],15:[function(require,module,exports){ +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var Descriptor = require("./descriptor.js"); +var SizeDescriptor = require("./size_descriptor.js"); +var Size = require("../values/size.js"); + +var Me = module.exports = function SizeMultiple(relativeTo, multiple) { + ensure.signature(arguments, [ Descriptor, Number ]); + + this._relativeTo = relativeTo; + this._multiple = multiple; +}; +SizeDescriptor.extend(Me); + +Me.create = function create(relativeTo, multiple) { + return new Me(relativeTo, multiple); +}; + +Me.prototype.value = function value() { + ensure.signature(arguments, []); + + return this._relativeTo.value().times(this._multiple); +}; + +Me.prototype.toString = function toString() { + ensure.signature(arguments, []); + + var multiple = this._multiple; + var base = this._relativeTo.toString(); + if (multiple === 1) return base; + + var desc; + switch(multiple) { + case 1/2: desc = "half of "; break; + case 1/3: desc = "one-third of "; break; + case 2/3: desc = "two-thirds of "; break; + case 1/4: desc = "one-quarter of "; break; + case 3/4: desc = "three-quarters of "; break; + case 1/5: desc = "one-fifth of "; break; + case 2/5: desc = "two-fifths of "; break; + case 3/5: desc = "three-fifths of "; break; + case 4/5: desc = "four-fifths of "; break; + case 1/6: desc = "one-sixth of "; break; + case 5/6: desc = "five-sixths of "; break; + case 1/8: desc = "one-eighth of "; break; + case 3/8: desc = "three-eighths of "; break; + case 5/8: desc = "five-eighths of "; break; + case 7/8: desc = "seven-eighths of "; break; + default: + if (multiple > 1) desc = multiple + " times "; + else desc = (multiple * 100) + "% of "; + } + + return desc + base; +}; +},{"../util/ensure.js":23,"../values/size.js":29,"./descriptor.js":5,"./size_descriptor.js":14}],16:[function(require,module,exports){ +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("../util/ensure.js"); +var PositionDescriptor = require("./position_descriptor.js"); +var Position = require("../values/position.js"); + +var TOP = "top"; +var RIGHT = "right"; +var BOTTOM = "bottom"; +var LEFT = "left"; + +var Me = module.exports = function ViewportEdge(position, frame) { + var QFrame = require("../q_frame.js"); // break circular dependency + ensure.signature(arguments, [ String, QFrame ]); + + if (position === LEFT || position === RIGHT) PositionDescriptor.x(this); + else if (position === TOP || position === BOTTOM) PositionDescriptor.y(this); + else ensure.unreachable("Unknown position: " + position); + + this._position = position; + this._frame = frame; +}; +PositionDescriptor.extend(Me); + +Me.top = factoryFn(TOP); +Me.right = factoryFn(RIGHT); +Me.bottom = factoryFn(BOTTOM); +Me.left = factoryFn(LEFT); + +Me.prototype.value = function() { + ensure.signature(arguments, []); + + var scroll = this._frame.getRawScrollPosition(); + var x = Position.x(scroll.x); + var y = Position.y(scroll.y); + + var size = viewportSize(this._frame.get("html").toDomElement()); + + switch(this._position) { + case TOP: return y; + case RIGHT: return x.plus(Position.x(size.width)); + case BOTTOM: return y.plus(Position.y(size.height)); + case LEFT: return x; + + default: ensure.unreachable(); + } +}; + +Me.prototype.toString = function() { + ensure.signature(arguments, []); + return this._position + " edge of viewport"; +}; + +function factoryFn(position) { + return function factory(frame) { + return new Me(position, frame); + }; +} + + + +// USEFUL READING: http://www.quirksmode.org/mobile/viewports.html +// and http://www.quirksmode.org/mobile/viewports2.html + +// BROWSERS TESTED: Safari 6.2.0 (Mac OS X 10.8.5); Mobile Safari 7.0.0 (iOS 7.1); Firefox 32.0.0 (Mac OS X 10.8); +// Firefox 33.0.0 (Windows 7); Chrome 38.0.2125 (Mac OS X 10.8.5); Chrome 38.0.2125 (Windows 7); IE 8, 9, 10, 11 + +// Width techniques I've tried: (Note: results are different in quirks mode) +// body.clientWidth +// body.offsetWidth +// body.getBoundingClientRect().width +// fails on all browsers: doesn't include margin +// body.scrollWidth +// works on Safari, Mobile Safari, Chrome +// fails on Firefox, IE 8, 9, 10, 11: doesn't include margin +// html.getBoundingClientRect().width +// html.offsetWidth +// works on Safari, Mobile Safari, Chrome, Firefox +// fails on IE 8, 9, 10: includes scrollbar +// html.scrollWidth +// html.clientWidth +// WORKS! Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11 + +// Height techniques I've tried: (Note that results are different in quirks mode) +// body.clientHeight +// body.offsetHeight +// body.getBoundingClientRect().height +// fails on all browsers: only includes height of content +// body getComputedStyle("height") +// fails on all browsers: IE8 returns "auto"; others only include height of content +// body.scrollHeight +// works on Safari, Mobile Safari, Chrome; +// fails on Firefox, IE 8, 9, 10, 11: only includes height of content +// html.getBoundingClientRect().height +// html.offsetHeight +// works on IE 8, 9, 10 +// fails on IE 11, Safari, Mobile Safari, Chrome: only includes height of content +// html.scrollHeight +// works on Firefox, IE 8, 9, 10, 11 +// fails on Safari, Mobile Safari, Chrome: only includes height of content +// html.clientHeight +// WORKS! Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11 +function viewportSize(htmlElement) { + return { + width: htmlElement.clientWidth, + height: htmlElement.clientHeight + }; +} + +},{"../q_frame.js":19,"../util/ensure.js":23,"../values/position.js":27,"./position_descriptor.js":11}],17:[function(require,module,exports){ +// Copyright (c) 2014-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("./util/ensure.js"); +var shim = require("./util/shim.js"); +var camelcase = require("../vendor/camelcase-1.0.1-modified.js"); +var ElementRendered = require("./descriptors/element_rendered.js"); +var ElementEdge = require("./descriptors/element_edge.js"); +var Center = require("./descriptors/center.js"); +var GenericSize = require("./descriptors/generic_size.js"); +var Assertable = require("./assertable.js"); + +var Me = module.exports = function QElement(domElement, frame, nickname) { + var QFrame = require("./q_frame.js"); // break circular dependency + ensure.signature(arguments, [Object, QFrame, String]); + + this._domElement = domElement; + this._nickname = nickname; + + this.frame = frame; + + // properties + this.rendered = ElementRendered.create(this); + + this.top = ElementEdge.top(this); + this.right = ElementEdge.right(this); + this.bottom = ElementEdge.bottom(this); + this.left = ElementEdge.left(this); + + this.center = Center.x(this.left, this.right, "center of " + this); + this.middle = Center.y(this.top, this.bottom, "middle of " + this); + + this.width = GenericSize.create(this.left, this.right, "width of " + this); + this.height = GenericSize.create(this.top, this.bottom, "height of " + this); +}; +Assertable.extend(Me); + +Me.prototype.getRawStyle = function(styleName) { + ensure.signature(arguments, [String]); + + var styles; + var result; + + // WORKAROUND IE 8: no getComputedStyle() + if (window.getComputedStyle) { + // WORKAROUND Firefox 40.0.3: must use frame's contentWindow (ref https://bugzilla.mozilla.org/show_bug.cgi?id=1204062) + styles = this.frame.toDomElement().contentWindow.getComputedStyle(this._domElement); + result = styles.getPropertyValue(styleName); + } + else { + styles = this._domElement.currentStyle; + result = styles[camelcase(styleName)]; + } + if (result === null || result === undefined) result = ""; + return result; +}; + +Me.prototype.getRawPosition = function() { + ensure.signature(arguments, []); + + // WORKAROUND IE 8: No TextRectangle.height or .width + var rect = this._domElement.getBoundingClientRect(); + return { + left: rect.left, + right: rect.right, + width: rect.width !== undefined ? rect.width : rect.right - rect.left, + + top: rect.top, + bottom: rect.bottom, + height: rect.height !== undefined ? rect.height : rect.bottom - rect.top + }; +}; + +Me.prototype.calculatePixelValue = function(sizeString) { + var dom = this._domElement; + if (dom.runtimeStyle !== undefined) return ie8Workaround(); + + var result; + var style = dom.style; + var oldPosition = style.position; + var oldLeft = style.left; + + style.position = "absolute"; + style.left = sizeString; + result = parseFloat(this.getRawStyle("left")); // parseInt strips of 'px' value + + style.position = oldPosition; + style.left = oldLeft; + return result; + + // WORKAROUND IE 8: getRawStyle() doesn't normalize values to px + // Based on code by Dean Edwards: http://disq.us/p/myl99x + function ie8Workaround() { + var runtimeStyleLeft = dom.runtimeStyle.left; + var styleLeft = dom.style.left; + + dom.runtimeStyle.left = dom.currentStyle.left; + dom.style.left = sizeString; + result = dom.style.pixelLeft; + + dom.runtimeStyle.left = runtimeStyleLeft; + dom.style.left = styleLeft; + return result; + } +}; + +Me.prototype.parent = function(nickname) { + ensure.signature(arguments, [[ undefined, String ]]); + if (nickname === undefined) nickname = "parent of " + this._nickname; + + if (this.equals(this.frame.body())) return null; + + var parent = this._domElement.parentElement; + if (parent === null) return null; + + return new Me(parent, this.frame, nickname); +}; + +Me.prototype.add = function(html, nickname) { + ensure.signature(arguments, [String, [undefined, String]]); + if (nickname === undefined) nickname = html + " in " + this._nickname; + + var tempElement = document.createElement("div"); + tempElement.innerHTML = shim.String.trim(html); + ensure.that( + tempElement.childNodes.length === 1, + "Expected one element, but got " + tempElement.childNodes.length + " (" + html + ")" + ); + + var insertedElement = tempElement.childNodes[0]; + this._domElement.appendChild(insertedElement); + return new Me(insertedElement, this.frame, nickname); +}; + +Me.prototype.remove = function() { + ensure.signature(arguments, []); + shim.Element.remove(this._domElement); +}; + +Me.prototype.toDomElement = function() { + ensure.signature(arguments, []); + return this._domElement; +}; + +Me.prototype.toString = function() { + ensure.signature(arguments, []); + return "'" + this._nickname + "'"; +}; + +Me.prototype.equals = function(that) { + ensure.signature(arguments, [Me]); + return this._domElement === that._domElement; +}; + +},{"../vendor/camelcase-1.0.1-modified.js":32,"./assertable.js":2,"./descriptors/center.js":4,"./descriptors/element_edge.js":6,"./descriptors/element_rendered.js":7,"./descriptors/generic_size.js":9,"./q_frame.js":19,"./util/ensure.js":23,"./util/shim.js":25}],18:[function(require,module,exports){ +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("./util/ensure.js"); +var QElement = require("./q_element.js"); + +var Me = module.exports = function QElementList(nodeList, frame, nickname) { + var QFrame = require("./q_frame.js"); // break circular dependency + ensure.signature(arguments, [ Object, QFrame, String ]); + + this._nodeList = nodeList; + this._frame = frame; + this._nickname = nickname; +}; + +Me.prototype.length = function length() { + ensure.signature(arguments, []); + + return this._nodeList.length; +}; + +Me.prototype.at = function at(requestedIndex, nickname) { + ensure.signature(arguments, [ Number, [undefined, String] ]); + + var index = requestedIndex; + var length = this.length(); + if (index < 0) index = length + index; + + ensure.that( + index >= 0 && index < length, + "'" + this._nickname + "'[" + requestedIndex + "] is out of bounds; list length is " + length + ); + var element = this._nodeList[index]; + + if (nickname === undefined) nickname = this._nickname + "[" + index + "]"; + return new QElement(element, this._frame, nickname); +}; + +Me.prototype.toString = function toString() { + ensure.signature(arguments, []); + + return "'" + this._nickname + "' list"; +}; +},{"./q_element.js":17,"./q_frame.js":19,"./util/ensure.js":23}],19:[function(require,module,exports){ +// Copyright (c) 2014-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. +"use strict"; + +var ensure = require("./util/ensure.js"); +var shim = require("./util/shim.js"); +var quixote = require("./quixote.js"); +var QElement = require("./q_element.js"); +var QElementList = require("./q_element_list.js"); +var QViewport = require("./q_viewport.js"); +var QPage = require("./q_page.js"); +var async = require("../vendor/async-1.4.2.js"); + +var Me = module.exports = function QFrame() { + ensure.signature(arguments, []); + + this._domElement = null; + this._loaded = false; + this._removed = false; +}; + +function loaded(self, width, height, src, stylesheets) { + self._loaded = true; + self._document = self._domElement.contentDocument; + self._originalBody = self._document.body.innerHTML; + self._originalWidth = width; + self._originalHeight = height; + self._originalSrc = src; + self._originalStylesheets = stylesheets; +} + +Me.create = function create(parentElement, options, callback) { + ensure.signature(arguments, [Object, [Object, Function], [undefined, Function]]); + if (callback === undefined) { + callback = options; + options = {}; + } + var width = options.width || 2000; + var height = options.height || 2000; + var src = options.src; + var stylesheets = options.stylesheet || []; + var css = options.css; + if (!shim.Array.isArray(stylesheets)) stylesheets = [ stylesheets ]; + + var frame = new Me(); + checkUrls(src, stylesheets, function(err) { + if (err) return callback(err); + + var iframe = insertIframe(parentElement, width, height); + shim.EventTarget.addEventListener(iframe, "load", onFrameLoad); + setIframeContent(iframe, src); + + frame._domElement = iframe; + setFrameLoadCallback(frame, callback); + }); + return frame; + + function onFrameLoad() { + // WORKAROUND Mobile Safari 7.0.0, Safari 6.2.0, Chrome 38.0.2125: frame is loaded synchronously + // We force it to be asynchronous here + setTimeout(function() { + loaded(frame, width, height, src, stylesheets); + loadStylesheets(frame, stylesheets, function() { + if (css) loadRawCSS(frame, options.css); + frame._frameLoadCallback(null, frame); + }); + }, 0); + } +}; + +function setFrameLoadCallback(frame, callback) { + frame._frameLoadCallback = callback; +} + +function checkUrls(src, stylesheets, callback) { + urlExists(src, function(err, srcExists) { + if (err) return callback(err); + if (!srcExists) return callback(error("src", src)); + + async.each(stylesheets, checkStylesheet, callback); + }); + + function checkStylesheet(url, callback2) { + urlExists(url, function(err, stylesheetExists) { + if (err) return callback2(err); + + if (!stylesheetExists) return callback2(error("stylesheet", url)); + else return callback2(null); + }); + } + + function error(name, url) { + return new Error("404 error while loading " + name + " (" + url + ")"); + } +} + +function urlExists(url, callback) { + var STATUS_AVAILABLE = 2; // WORKAROUND IE 8: non-standard XMLHttpRequest constant names + + if (url === undefined) { + return callback(null, true); + } + + var http = new XMLHttpRequest(); + http.open("HEAD", url); + http.onreadystatechange = function() { // WORKAROUND IE 8: doesn't support .addEventListener() or .onload + if (http.readyState === STATUS_AVAILABLE) { + return callback(null, http.status !== 404); + } + }; + http.onerror = function() { // onerror handler is not tested + return callback("XMLHttpRequest error while using HTTP HEAD on URL '" + url + "': " + http.statusText); + }; + http.send(); +} + +function insertIframe(parentElement, width, height) { + var iframe = document.createElement("iframe"); + iframe.setAttribute("width", width); + iframe.setAttribute("height", height); + iframe.setAttribute("frameborder", "0"); // WORKAROUND IE 8: don't include frame border in position calcs + parentElement.appendChild(iframe); + return iframe; +} + +function setIframeContent(iframe, src) { + if (src === undefined) { + writeStandardsModeHtml(iframe); + } else { + setIframeSrc(iframe, src); + } +} + +function setIframeSrc(iframe, src) { + iframe.setAttribute("src", src); +} + +function writeStandardsModeHtml(iframe) { + var standardsMode = "\n"; + iframe.contentWindow.document.open(); + iframe.contentWindow.document.write(standardsMode); + iframe.contentWindow.document.close(); +} + +function loadStylesheets(self, urls, callback) { + async.each(urls, addLinkTag, callback); + + function addLinkTag(url, onLinkLoad) { + var link = document.createElement("link"); + shim.EventTarget.addEventListener(link, "load", function(event) { onLinkLoad(null); }); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("type", "text/css"); + link.setAttribute("href", url); + shim.Document.head(self._document).appendChild(link); + } +} + +function loadRawCSS(self, css) { + var style = document.createElement("style"); + style.setAttribute("type", "text/css"); + if (style.styleSheet) { + // WORKAROUND IE 8: Throws 'unknown runtime error' if you set innerHTML on a
"); + + var text = frame.add("

arbitrary text

"); + frame.add("

must have two p tags to work

"); + + // WORKAROUND IE 8: need to force reflow or getting font-size may fail below + // This seems to occur when IE is running in a slow VirtualBox VM. There is no test for this line. + var forceReflow = text.offsetHeight; + + // WORKAROUND Safari 8.0.0: timeout required because font is enlarged asynchronously + setTimeout(function() { + var fontSize = text.getRawStyle("font-size"); + ensure.that(fontSize !== "", "Expected font-size to be a value"); + + // WORKAROUND IE 8: ignores "); + + var text = frame.add("

arbitrary text

"); + frame.add("

must have two p tags to work

"); + + // WORKAROUND IE 8: need to force reflow or getting font-size may fail below + // This seems to occur when IE is running in a slow VirtualBox VM. There is no test for this line. + var forceReflow = text.offsetHeight; + + // WORKAROUND Safari 8.0.0: timeout required because font is enlarged asynchronously + setTimeout(function() { + var fontSize = text.getRawStyle("font-size"); + ensure.that(fontSize !== "", "Expected font-size to be a value"); + + // WORKAROUND IE 8: ignores "); - - var text = frame.add("

arbitrary text

"); - frame.add("

must have two p tags to work

"); - - // WORKAROUND IE 8: need to force reflow or getting font-size may fail below - // This seems to occur when IE is running in a slow VirtualBox VM. There is no test for this line. - var forceReflow = text.offsetHeight; - - // WORKAROUND Safari 8.0.0: timeout required because font is enlarged asynchronously - setTimeout(function() { - var fontSize = text.getRawStyle("font-size"); - ensure.that(fontSize !== "", "Expected font-size to be a value"); - - // WORKAROUND IE 8: ignores "); - - var text = frame.add("

arbitrary text

"); - frame.add("

must have two p tags to work

"); - - // WORKAROUND IE 8: need to force reflow or getting font-size may fail below - // This seems to occur when IE is running in a slow VirtualBox VM. There is no test for this line. - var forceReflow = text.offsetHeight; - - // WORKAROUND Safari 8.0.0: timeout required because font is enlarged asynchronously - setTimeout(function() { - var fontSize = text.getRawStyle("font-size"); - ensure.that(fontSize !== "", "Expected font-size to be a value"); - - // WORKAROUND IE 8: ignores "); + + var text = frame.add("

arbitrary text

"); + frame.add("

must have two p tags to work

"); + + // WORKAROUND IE 8: need to force reflow or getting font-size may fail below + // This seems to occur when IE is running in a slow VirtualBox VM. There is no test for this line. + var forceReflow = text.offsetHeight; + + // WORKAROUND Safari 8.0.0: timeout required because font is enlarged asynchronously + setTimeout(function() { + var fontSize = text.getRawStyle("font-size"); + ensure.that(fontSize !== "", "Expected font-size to be a value"); + + // WORKAROUND IE 8: ignores