├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── ROADMAP.md ├── build ├── config │ ├── branches.js │ ├── build_command.js │ ├── eslint.conf.js │ ├── karma.conf.js │ ├── paths.js │ └── tested_browsers.js ├── scripts │ ├── build.jakefile.js │ ├── integrate.jakefile.js │ ├── readme.md │ ├── release.jakefile.js │ └── run_jake.sh └── util │ ├── browserify_runner.js │ ├── git_runner.js │ ├── lint_runner.js │ ├── sh.js │ └── version_checker.js ├── dist └── quixote.js ├── docs ├── ElementRender.md ├── PositionDescriptor.md ├── QElement.md ├── QElementList.md ├── QFrame.md ├── QPage.md ├── QViewport.md ├── README.md ├── SizeDescriptor.md ├── Span.md ├── api.md ├── index.html └── quixote.md ├── example ├── .gitignore ├── LICENSE.txt ├── build │ ├── config │ │ ├── build_command.js │ │ ├── jshint.conf.js │ │ ├── karma.conf.js │ │ ├── paths.js │ │ └── tested_browsers.js │ ├── scripts │ │ ├── build.jakefile.js │ │ ├── run_jake.bat │ │ ├── run_jake.sh │ │ └── watch.js │ └── util │ │ ├── browserify_runner.js │ │ ├── karma_runner.js │ │ ├── mocha_runner.js │ │ ├── sh.js │ │ └── version_checker.js ├── jake.bat ├── jake.sh ├── package-lock.json ├── package.json ├── readme.md ├── src │ ├── _media_css_test.js │ ├── _toggle_test.js │ ├── assert.js │ ├── icon.svg │ ├── index.html │ ├── screen.css │ └── toggle.js ├── vendor │ ├── chai-2.1.0.js │ └── quixote.js ├── video_poster.jpg ├── watch.bat └── watch.sh ├── integrate.sh ├── jake.sh ├── package-lock.json ├── package.json ├── release.sh ├── spikes ├── ie8_iframe_quirks │ ├── README.md │ ├── index.html │ └── inner.html ├── ios_frame_scrolling │ ├── README.md │ ├── index.html │ └── inner.html ├── ios_frame_sizing │ ├── README.md │ ├── index.html │ └── inner.html └── ios_text_sizing │ ├── README.md │ ├── index.html │ └── inner.html ├── src ├── __reset.css ├── __reset.js ├── _assertable_test.js ├── _browser_test.js ├── _browsing_context_test.js ├── _q_element_list_test.js ├── _q_element_test.js ├── _q_frame_test.css ├── _q_frame_test.html ├── _q_frame_test.js ├── _q_frame_test2.css ├── _q_page_test.js ├── _q_viewport_test.js ├── _quixote_test.js ├── assertable.js ├── browser.js ├── browsing_context.js ├── descriptors │ ├── README.md │ ├── _absolute_position_test.js │ ├── _center_test.js │ ├── _descriptor_test.js │ ├── _element_edge_test.js │ ├── _element_render_edge_test.js │ ├── _element_render_test.js │ ├── _page_edge_test.js │ ├── _position_descriptor_test.js │ ├── _relative_position_test.js │ ├── _relative_size_test.js │ ├── _size_descriptor_test.js │ ├── _size_multiple_test.js │ ├── _span_test.js │ ├── _viewport_edge_test.js │ ├── absolute_position.js │ ├── center.js │ ├── descriptor.js │ ├── element_edge.js │ ├── element_render.js │ ├── element_render_edge.js │ ├── page_edge.js │ ├── position_descriptor.js │ ├── relative_position.js │ ├── relative_size.js │ ├── size_descriptor.js │ ├── size_multiple.js │ ├── span.js │ └── viewport_edge.js ├── q_element.js ├── q_element_list.js ├── q_frame.js ├── q_page.js ├── q_viewport.js ├── quixote.js ├── util │ ├── _ensure_test.js │ ├── _oop_test.js │ ├── assert.js │ ├── big_object_diff.js │ ├── ensure.js │ ├── oop.js │ └── shim.js └── values │ ├── README.md │ ├── _pixels_test.js │ ├── _position_test.js │ ├── _render_state_test.js │ ├── _size_test.js │ ├── _value_test.js │ ├── pixels.js │ ├── position.js │ ├── render_state.js │ ├── size.js │ └── value.js ├── test ├── README.md └── _assertion_test.js ├── vendor ├── async-1.4.2.js ├── camelcase-1.0.1-modified.js └── proclaim-2.0.0.js └── watch.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | generated/ -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | License (The MIT License) 2 | ------- 3 | Copyright (c) 2014-2015 Titanium I.T. LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | 24 | 25 | Third Party Code: 26 | ----------------- 27 | 28 | * 'proclaim' licensed under MIT license (https://github.com/rowanmanning/proclaim) 29 | * 'camelcase' © Sindre Sorhus, licensed under MIT license (https://github.com/sindresorhus/camelcase) 30 | * 'async' Copyright 2010-2014 Caolan McMahon, licensed under MIT license (https://github.com/caolan/async) -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Quixote Roadmap 2 | 3 | ## Release Ideas 4 | 5 | * **✅ v0.1** Basic DOM manipulation; raw position and style information 6 | * **✅ v0.2** "Cooked" absolute position info; initial assertion API 7 | * **✅ v0.3** Positioning relative to other elements 8 | * **✅ v0.4** Advanced positioning (middle, center, height, width, arithmetic, fractions) 9 | * **✅ v0.5** API hardening 10 | * **✅ v0.6** Responsive design 11 | * **✅ v0.7** Page and viewport assertions 12 | * **✅ v0.8 - v0.11** Dogfooding and real-world usage (more dogfooding releases to come) 13 | * **✅ v0.12** Element display status descriptors 14 | * **✅ v0.13** Element rendering boundaries 15 | * **✅ v0.14** QFrame quality of life improvements 16 | * **✅ v0.15** Support for third-party test runners 17 | * **✅ v1.0.0** New assertions 18 | * See our [work-in-progress roadmap](https://github.com/jamesshore/quixote/blob/master/ROADMAP.md) for the latest release plans. 19 | 20 | 21 | ## Current Feature: TBD 22 | 23 | 24 | ## To Do: TBD 25 | 26 | 27 | ## Dogfooding Notes 28 | 29 | * Provide a better way of integrating with standard assertion libraries? Use `valueOf()`? 30 | * Provide better error message when cross-origin 'src' provided to quixote.createFrame 31 | 32 | 33 | ## Future Features 34 | 35 | * Z-ordering 36 | * Colors? Contrast (fg color vs. bg color?)) 37 | * Plugin API 38 | -------------------------------------------------------------------------------- /build/config/branches.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. 2 | "use strict"; 3 | 4 | module.exports = { 5 | dev: "dev", 6 | integration: "master", 7 | release: "release" 8 | }; -------------------------------------------------------------------------------- /build/config/build_command.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. 2 | "use strict"; 3 | 4 | module.exports = "./jake.sh"; -------------------------------------------------------------------------------- /build/config/eslint.conf.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. 2 | "use strict"; 3 | 4 | let ERROR = "error"; 5 | 6 | exports.options = { 7 | "parserOptions": { 8 | "ecmaVersion": 2017 9 | }, 10 | 11 | "env": { 12 | "browser": true, 13 | "node": true, 14 | "commonjs": true, 15 | "mocha": true 16 | }, 17 | 18 | "globals": { 19 | "Promise": false, 20 | 21 | // Jake 22 | jake: false, 23 | desc: false, 24 | task: false, 25 | complete: false, 26 | fail: false, 27 | rule: false, 28 | directory: false, 29 | file: false 30 | }, 31 | 32 | "rules": { 33 | // "Possible Errors" (according to ESLint docs) 34 | "no-compare-neg-zero": ERROR, 35 | "no-cond-assign": ERROR, 36 | "no-constant-condition": ERROR, 37 | "no-control-regex": ERROR, 38 | "no-dupe-args": ERROR, 39 | "no-dupe-keys": ERROR, 40 | "no-duplicate-case": ERROR, 41 | "no-empty-character-class": ERROR, 42 | "no-ex-assign": ERROR, 43 | "no-extra-boolean-cast": ERROR, 44 | "no-extra-semi": ERROR, 45 | "no-func-assign": ERROR, 46 | "no-inner-declarations": ERROR, 47 | "no-invalid-regexp": ERROR, 48 | "no-irregular-whitespace": ERROR, 49 | "no-obj-calls": ERROR, 50 | "no-sparse-arrays": ERROR, 51 | "no-template-curly-in-string": ERROR, 52 | "no-unexpected-multiline": ERROR, 53 | "no-unsafe-finally": ERROR, 54 | "no-unsafe-negation": ERROR, 55 | "use-isnan": ERROR, 56 | "valid-typeof": ERROR, 57 | 58 | // "Best Practices" 59 | "eqeqeq": ERROR, 60 | "no-caller": ERROR, 61 | "no-case-declarations": ERROR, 62 | "no-empty-pattern": ERROR, 63 | "no-eq-null": ERROR, 64 | "no-eval": ERROR, 65 | "no-extend-native": ERROR, 66 | "no-fallthrough": ERROR, 67 | "no-global-assign": ERROR, 68 | "no-implicit-globals": ERROR, 69 | "no-implied-eval": ERROR, 70 | "no-iterator": ERROR, 71 | "no-loop-func": ERROR, 72 | "no-octal": ERROR, 73 | "no-octal-escape": ERROR, 74 | "no-proto": ERROR, 75 | "no-redeclare": ERROR, 76 | "no-restricted-properties": ERROR, 77 | "no-return-assign": ERROR, 78 | "no-return-await": ERROR, 79 | "no-script-url": ERROR, 80 | "no-self-assign": ERROR, 81 | "no-self-compare": ERROR, 82 | "no-throw-literal": ERROR, 83 | "no-useless-escape": ERROR, 84 | "no-with": ERROR, 85 | "prefer-promise-reject-errors": ERROR, 86 | "radix": ERROR, 87 | "require-await": ERROR, 88 | 89 | // "Strict Mode" 90 | "strict": [ ERROR, "global" ], 91 | 92 | // "Variables" 93 | "no-delete-var": ERROR, 94 | "no-undef": ERROR, 95 | "no-undef-init": ERROR, 96 | "no-use-before-define": [ ERROR, { "functions": false, "classes": true, "variables": false} ], 97 | 98 | // "Stylistic Issues" 99 | "new-cap": ERROR, 100 | "no-bitwise": ERROR, 101 | "no-mixed-spaces-and-tabs": ERROR, 102 | "semi": [ ERROR, "always" ], 103 | 104 | // "ECMAScript 6" 105 | "constructor-super": ERROR, 106 | "no-class-assign": ERROR, 107 | "no-confusing-arrow": ERROR, 108 | "no-const-assign": ERROR, 109 | "no-dupe-class-members": ERROR, 110 | "no-duplicate-imports": ERROR, 111 | "no-new-symbol": ERROR, 112 | "no-this-before-super": ERROR, 113 | "require-yield": ERROR 114 | } 115 | }; 116 | -------------------------------------------------------------------------------- /build/config/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Mon Sep 29 2014 15:15:28 GMT-0700 (PDT) 3 | "use strict"; 4 | 5 | module.exports = function(config) { 6 | config.set({ 7 | 8 | // base path that will be used to resolve all patterns (eg. files, exclude) 9 | basePath: '../../', 10 | 11 | 12 | // frameworks to use 13 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 14 | frameworks: ['mocha', 'commonjs'], 15 | 16 | 17 | // list of files / patterns to load in the browser 18 | files: [ 19 | 'src/**/*.js', 20 | 'src/**/*.html', 21 | 'src/**/*.css', 22 | 'vendor/**/*.js', 23 | 'test/**/*.js' 24 | ], 25 | 26 | 27 | // list of files to exclude 28 | exclude: [], 29 | 30 | 31 | // preprocess matching files before serving them to the browser 32 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 33 | preprocessors: { 34 | // this glob avoids processing the *_script_test.js files, which will be loaded as 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /example/src/screen.css: -------------------------------------------------------------------------------- 1 | /* Our CSS file. 2 | 3 | 4 | /* The media object. We use Quixote to test it in `_media_css_test.js`. */ 5 | 6 | .media:after { 7 | content: ""; 8 | display: table; 9 | clear: both; 10 | } 11 | 12 | .media__figure { 13 | float: left; 14 | margin-right: 10px; 15 | } 16 | 17 | .media__content { 18 | overflow: hidden; 19 | } 20 | 21 | 22 | /* Our example code also includes a simple JavaScript library for toggling the visibility of an element. */ 23 | /* The .invisible class is used in that example. It's not related to the Quixote tests. */ 24 | 25 | .invisible { 26 | visibility: hidden; 27 | } 28 | -------------------------------------------------------------------------------- /example/src/toggle.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. 2 | 3 | // A simple JavaScript library to toggle the visibility of an element. It's tested by `_toggle_test.js`. 4 | // There's nothing related to Quixote in this file. 5 | 6 | 7 | (function() { 8 | "use strict"; 9 | 10 | exports.init = function init(clickMe, element, className) { 11 | clickMe.addEventListener("click", function() { 12 | element.classList.toggle(className); 13 | }); 14 | }; 15 | 16 | }()); -------------------------------------------------------------------------------- /example/video_poster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesshore/quixote/6b5c07b4d202d44e0ee6ecd99c22df4547558c17/example/video_poster.jpg -------------------------------------------------------------------------------- /example/watch.bat: -------------------------------------------------------------------------------- 1 | @node build/scripts/watch.js %* -------------------------------------------------------------------------------- /example/watch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | node build/scripts/watch.js $* -------------------------------------------------------------------------------- /integrate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . build/scripts/run_jake.sh -f build/scripts/integrate.jakefile.js $* 3 | -------------------------------------------------------------------------------- /jake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . build/scripts/run_jake.sh -f build/scripts/build.jakefile.js $* 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quixote", 3 | "version": "1.0.1", 4 | "description": "CSS unit and integration testing", 5 | "main": "src/quixote.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "browserify": "^16.5.1", 9 | "eslint": "^6.8.0", 10 | "gaze": "^1.1.3", 11 | "glob": "^7.1.6", 12 | "jake": "^8.1.1", 13 | "karma": "^1.3.0", 14 | "karma-commonjs": "1.0.0", 15 | "karma-firefox-launcher": "^1.3.0", 16 | "karma-mocha": "^1.3.0", 17 | "mocha": "^3.5.3", 18 | "semver": "^7.2.1", 19 | "shelljs": "^0.8.3", 20 | "simplebuild-karma": "^1.0.0" 21 | }, 22 | "scripts": { 23 | "test": "echo '*** Read CONTRIBUTING.md for a better way to test quixote ***' && ./jake.sh loose=true capture=Firefox" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/jamesshore/quixote.git" 28 | }, 29 | "keywords": [ 30 | "css", 31 | "test", 32 | "tdd" 33 | ], 34 | "author": "James Shore ", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/jamesshore/quixote/issues" 38 | }, 39 | "homepage": "https://github.com/jamesshore/quixote" 40 | } 41 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . build/scripts/run_jake.sh -f build/scripts/release.jakefile.js $* 3 | -------------------------------------------------------------------------------- /spikes/ie8_iframe_quirks/README.md: -------------------------------------------------------------------------------- 1 | These files demonstrate an issue with iframes when IE 8 is in "quirks" mode. 2 | 3 | To use, serve the files from a web server (I like npm's `http-server` for simplicity and convenience), then 4 | visit index.html in IE 8 or any other browser. 5 | 6 | Observe: 7 | * The red 'full width' element does not extend the entire width of the frame in IE 8. 8 | 9 | Then uncomment the DOCTYPE at the top of inner.html. This turns off "quirks" mode. 10 | 11 | Observe: 12 | * The red 'full width' element now extends the entire width of the frame. -------------------------------------------------------------------------------- /spikes/ie8_iframe_quirks/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /spikes/ie8_iframe_quirks/inner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
full width
10 | 11 | 12 | -------------------------------------------------------------------------------- /spikes/ios_frame_scrolling/README.md: -------------------------------------------------------------------------------- 1 | These files demonstrate how to make an iframe scrollable on Mobile Safari. 2 | 3 | To use, serve the files from a web server (I like npm's `http-server` for simplicity and convenience), then 4 | visit index.html in Mobile Safari or any other browser. 5 | 6 | Observe: 7 | * The frame has scroll bars. This is not the default for Mobile Safari. (Thanks to David Walsh, http://davidwalsh.name/scroll-iframes-ios, for this part of the solution.) 8 | * Clicking the button scrolls the frame. This requires special code on Mobile Safari. 9 | -------------------------------------------------------------------------------- /spikes/ios_frame_scrolling/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | 27 |
28 | 29 | 30 |
31 | 32 | 33 | 34 |
Status: 35 |
    36 |
    37 | 38 | -------------------------------------------------------------------------------- /spikes/ios_frame_scrolling/inner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 |
    Top left
    18 |
    Under
    19 |
    Scroll
    20 | 21 | 22 | -------------------------------------------------------------------------------- /spikes/ios_frame_sizing/README.md: -------------------------------------------------------------------------------- 1 | These files demonstrate how Mobile Safari sizes an iframe. 2 | 3 | To use, serve the files from a web server (I like npm's `http-server` for simplicity and convenience), then 4 | visit index.html in Mobile Safari or any other browser. 5 | 6 | On Mobile Safari, observe: 7 | * The frame ignores the `height` and `width` attributes. 8 | * The red full-width element extends all the way to the scroll-creator box. On a desktop browser, the full-width element only extends the width of the viewport. 9 | * The blue `@media successful` box does not appear. (It's styled to appear when the width of the viewport is <= 1000px.) 10 | 11 | Now load inner.html in Mobile Safari. Observe that the page behaves similarly to a desktop browser: 12 | * The red full-width element does *not* extend the entire with of the page. It only extends the width of the viewport (980px on iOS, unless otherwise configured with a `` tag). 13 | * The blue `@media successful` box *does* appear. 14 | 15 | Now comment out the "scroll creator" line in inner.html. Load inner.html (to flush the cache) and then index.html. On index.html, observe that the page matches the iframe's width and height. 16 | * The frame obeys the `width` and `height` attributes on Mobile Safari. 17 | * The red full-width element is only 400px wide 18 | * The blue `@media successful` box does appear. 19 | 20 | 21 | CONCLUSION: 22 | 23 | Mobile Safari sizes a frame to the frame size attributes *or* the actual page size, whichever is *larger*. 24 | 25 | This is in contrast to a desktop browser, which always sizes a frame its attributes, regardless of the page size. -------------------------------------------------------------------------------- /spikes/ios_frame_sizing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spikes/ios_frame_sizing/inner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 32 | 33 | 34 | 35 |
    100% width
    36 |
    Scroll creator
    37 |
    @media successful
    38 | 39 | 40 | -------------------------------------------------------------------------------- /spikes/ios_text_sizing/README.md: -------------------------------------------------------------------------------- 1 | These files demonstrate how Mobile Safari sizes an iframe. 2 | 3 | To use, serve the files from a web server (I like npm's `http-server` for simplicity and convenience), then 4 | visit index.html in Mobile Safari or any other browser. 5 | 6 | On Mobile Safari, observe: 7 | * The top frame, which extends past the edge of the window, has larger text than the bottom frame, which doesn't. 8 | 9 | 10 | CONCLUSION: 11 | 12 | Mobile Safari will increase the size of text in a frame when the frame extends past the iPhone edge. This behavior can be changed by setting `-webkit-text-size-adjust: 100%` in CSS. Using `-webkit-text-size-adjust: none` will also work, but there are reports that it prevents the text size from being changed in some browsers. 13 | -------------------------------------------------------------------------------- /spikes/ios_text_sizing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

    In this frame, the text is made larger than specified by its stylesheet.

    9 | 10 | 11 |

    This is the same frame content in a narrower frame. In this one, the text is exactly the size specified by its stylesheet.

    12 | 13 | 14 |

    Link to inner file (may be needed for Mobile Safari cache-busting)

    15 | 16 | 17 | -------------------------------------------------------------------------------- /spikes/ios_text_sizing/inner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 |

    This is a line of text inside a frame.

    17 |

    This is another line of text inside a frame.

    18 | 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/__reset.css: -------------------------------------------------------------------------------- 1 | body, p, div { 2 | margin: 0; 3 | border: 0; 4 | padding: 0; 5 | } -------------------------------------------------------------------------------- /src/__reset.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. 2 | "use strict"; 3 | 4 | // Set up a simple 'reset stylesheet' test frame for all tests to use. 5 | // Because there's no "describe" block in this file, the 'before' and 'after' run before and after 6 | // the entire test suite, and the 'beforeEach' runs before every test. 7 | // 8 | // This reduces the number of times frames are created and destroyed, which speeds up the tests. 9 | 10 | var quixote = require("./quixote.js"); 11 | 12 | exports.WIDTH = 500; 13 | exports.HEIGHT = 400; 14 | 15 | mocha.timeout(20000); 16 | 17 | before(function(done) { 18 | var options = { 19 | width: exports.WIDTH, 20 | height: exports.HEIGHT, 21 | stylesheet: "/base/src/__reset.css" 22 | }; 23 | 24 | exports.frame = quixote.createFrame(options, done); 25 | }); 26 | 27 | beforeEach(function() { 28 | exports.frame.reset(); 29 | }); -------------------------------------------------------------------------------- /src/_assertable_test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. 2 | "use strict"; 3 | 4 | var assert = require("./util/assert.js"); 5 | var Assertable = require("./assertable.js"); 6 | var reset = require("./__reset.js"); 7 | 8 | describe("FOUNDATION: Assertable abstract base class", function() { 9 | 10 | var TOP = 10; 11 | var RIGHT = 150; 12 | var BOTTOM = 70; 13 | var LEFT = 20; 14 | 15 | var frame; 16 | var element; 17 | 18 | beforeEach(function() { 19 | frame = reset.frame; 20 | frame.add( 21 | "

    one

    " 22 | ); 23 | element = frame.get("#element"); 24 | }); 25 | 26 | it("can be extended", function() { 27 | function Subclass() {} 28 | 29 | Assertable.extend(Subclass); 30 | assert.type(new Subclass(), Assertable); 31 | }); 32 | 33 | it("diffs one property", function() { 34 | var expected = element.top.diff(600); 35 | assert.equal(element.diff({ top: 600 }), expected, "difference"); 36 | assert.equal(element.diff({ top: TOP }), "", "no difference"); 37 | }); 38 | 39 | it("diffs multiple properties", function() { 40 | var topDiff = element.top.diff(600); 41 | var rightDiff = element.right.diff(400); 42 | var bottomDiff = element.bottom.diff(200); 43 | 44 | assert.equal( 45 | element.diff({ top: 600, right: 400, bottom: 200 }), 46 | topDiff + "\n" + rightDiff + "\n" + bottomDiff, 47 | "three differences" 48 | ); 49 | assert.equal(element.diff({ top: TOP, right: RIGHT, bottom: BOTTOM }), "", "no differences"); 50 | assert.equal( 51 | element.diff({ top: 600, right: RIGHT, bottom: 200}), 52 | topDiff + "\n" + bottomDiff, 53 | "two differences, with middle one okay" 54 | ); 55 | assert.equal(element.diff({ top: TOP, right: RIGHT, bottom: 200}), bottomDiff, "one difference"); 56 | }); 57 | 58 | it("supports relative comparisons", function() { 59 | var two = frame.add("
    two
    "); 60 | assert.equal(element.diff({ top: two.top }), "", "relative diff"); 61 | }); 62 | 63 | it("has variant that throws an exception when differences found", function() { 64 | var diff = element.diff({ top: 600 }); 65 | 66 | assert.noException(function() { 67 | element.assert({ top: TOP }); 68 | }, "same"); 69 | 70 | assert.exception(function() { 71 | element.assert({ top: 600 }); 72 | }, "Differences found:\n" + diff + "\n", "different"); 73 | 74 | assert.exception(function() { 75 | element.assert({ top: 600 }, "a message"); 76 | }, "a message:\n" + diff + "\n", "different, with a message"); 77 | }); 78 | 79 | }); -------------------------------------------------------------------------------- /src/_browser_test.js: -------------------------------------------------------------------------------- 1 | // Copyright Titanium I.T. LLC. 2 | "use strict"; 3 | 4 | var assert = require("./util/assert.js"); 5 | var quixote = require("./quixote.js"); 6 | 7 | describe("FOUNDATION: Browser capability", function() { 8 | 9 | var mobileSafari; 10 | var chromeMobile; 11 | var ie8; 12 | var ie11; 13 | 14 | beforeEach(function() { 15 | var userAgent = navigator.userAgent; 16 | 17 | // These user agent strings may be brittle. It's okay because we only use them in the tests. Modify them 18 | // as needed to make sure tests match real-world behavior. 19 | mobileSafari = userAgent.match(/(iPad|iPhone|iPod touch);/i) !== null; 20 | chromeMobile = userAgent.match(/Android/) !== null; 21 | ie8 = userAgent.match(/MSIE 8\.0/) !== null; 22 | ie11 = userAgent.match(/rv:11\.0/) !== null; 23 | }); 24 | 25 | it("detects whether browser expands frame to fit size of page", function() { 26 | assert.equal( 27 | quixote.browser.enlargesFrameToPageSize(), 28 | false, 29 | "everything should respect frame size" 30 | ); 31 | }); 32 | 33 | it("detects whether browser expands size of font when frame is large", function() { 34 | assert.equal( 35 | quixote.browser.enlargesFonts(), 36 | mobileSafari, 37 | "everything but Mobile Safari should respect frame size" 38 | ); 39 | }); 40 | 41 | it("detects whether browser can detect `clip: auto` value", function() { 42 | assert.equal( 43 | quixote.browser.misreportsClipAutoProperty(), 44 | ie8, 45 | "everything but IE 8 should calculate 'clip: auto' properly" 46 | ); 47 | }); 48 | 49 | it("detects whether browser computes `clip: rect(auto, ...)` value correctly", function() { 50 | assert.equal( 51 | quixote.browser.misreportsAutoValuesInClipProperty(), 52 | ie11, 53 | "everything but IE 11 should calculate clip values properly" 54 | ); 55 | }); 56 | 57 | it("detects whether browser rounds off floating-point pixel values", function() { 58 | assert.equal( 59 | quixote.browser.roundsOffPixelCalculations(), 60 | ie8 || ie11, 61 | "only IE 8 and IE 11 should round off pixel values" 62 | ); 63 | }); 64 | 65 | }); -------------------------------------------------------------------------------- /src/_browsing_context_test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. 2 | "use strict"; 3 | 4 | var assert = require("./util/assert.js"); 5 | var reset = require("./__reset.js"); 6 | var BrowsingContext = require("./browsing_context.js"); 7 | var QElement = require("./q_element.js"); 8 | var QViewport = require("./q_viewport.js"); 9 | var QPage = require("./q_page.js"); 10 | 11 | 12 | describe("FOUNDATION: BrowsingContext", function() { 13 | 14 | describe("instance", function() { 15 | 16 | var frame; 17 | var browsingContext; 18 | 19 | before(function() { 20 | frame = reset.frame; 21 | browsingContext = frame.toBrowsingContext(); 22 | }); 23 | 24 | it("compares to another BrowsingContext", function() { 25 | var browsingContext1 = new BrowsingContext(frame.toDomElement().contentDocument); 26 | var browsingContext2 = new BrowsingContext(document); 27 | 28 | assert.objEqual(browsingContext, browsingContext1, "equality"); 29 | assert.objNotEqual(browsingContext, browsingContext2, "inequality"); 30 | }); 31 | 32 | it("provides access to viewport descriptors", function() { 33 | assert.type(browsingContext.viewport(), QViewport); 34 | }); 35 | 36 | it("provides access to page descriptors", function() { 37 | assert.type(browsingContext.page(), QPage); 38 | }); 39 | 40 | it("provides access to raw HTML", function() { 41 | assert.equal(browsingContext.contentWindow, frame.toDomElement().contentWindow); 42 | }); 43 | 44 | it("retrieves body element", function() { 45 | assert.objEqual( 46 | browsingContext.body(), 47 | QElement.create(frame.toDomElement().contentDocument.body, ""), 48 | "body element" 49 | ); 50 | assert.equal(browsingContext.body().toString(), "''", "body description"); 51 | }); 52 | 53 | it("adds an element", function() { 54 | var element = browsingContext.add("

    foo

    "); 55 | var body = browsingContext.body(); 56 | 57 | assert.objEqual(element.parent(), body, "element should be present in browsingContext body"); 58 | assert.equal(element.toString(), "'#myId'", "should generate default nickname"); 59 | }); 60 | 61 | it("uses optional nickname to describe added elements", function() { 62 | var element = browsingContext.add("

    foo

    ", "my element"); 63 | assert.equal(element.toString(), "'my element'"); 64 | }); 65 | 66 | it("retrieves an element by selector", function() { 67 | var expected = browsingContext.add("
    Irrelevant text
    "); 68 | var byId = browsingContext.get("#foo"); 69 | var byClass = browsingContext.get(".bar"); 70 | var byAttribute = browsingContext.get("[baz]"); 71 | 72 | assert.objEqual(byId, expected, "should get element by ID"); 73 | assert.objEqual(byClass, expected, "should get element by class"); 74 | assert.objEqual(byAttribute, expected, "should get element by attribute"); 75 | 76 | assert.equal(byId.toString(), "'#foo'", "should describe element by selector used"); 77 | }); 78 | 79 | it("uses optional nickname to describe retrieved elements", function() { 80 | browsingContext.add("
    Irrelevant text
    "); 81 | var element = browsingContext.get("#foo", "Bestest Element Ever!!"); 82 | assert.equal(element.toString(), "'Bestest Element Ever!!'"); 83 | }); 84 | 85 | it("fails fast when retrieving non-existant element", function() { 86 | assert.exception(function() { 87 | browsingContext.get(".blah"); 88 | }, /Expected one element to match '\.blah', but found 0/); 89 | }); 90 | 91 | it("fails fast when retrieving too many elements", function() { 92 | browsingContext.add("

    One

    Two

    "); 93 | 94 | assert.exception(function() { 95 | browsingContext.get("p"); 96 | }, /Expected one element to match 'p', but found 2/); 97 | }); 98 | 99 | it("retrieves a list of elements", function() { 100 | browsingContext.add("

    One

    Two

    Three

    "); 101 | var some = browsingContext.getAll("p"); 102 | var named = browsingContext.getAll("p", "my name"); 103 | 104 | assert.objEqual(some.at(0), browsingContext.get("#p1"), "should get a working list"); 105 | assert.equal(some.toString(), "'p' list", "should describe it by its selector"); 106 | assert.equal(named.toString(), "'my name' list", "should use nickname when provided"); 107 | }); 108 | 109 | it("scrolls", function() { 110 | browsingContext.add("
    scroll enabler
    "); 111 | 112 | assert.deepEqual(browsingContext.getRawScrollPosition(), { x: 0, y: 0 }, "should start at (0, 0)"); 113 | 114 | browsingContext.scroll(150, 300); 115 | var position = browsingContext.getRawScrollPosition(); 116 | 117 | // WORKAROUND Chrome Mobile 74: scrolls to a fractional positions (149.7142791748047, 299.80950927734375), 118 | // so we round it off 119 | assert.equal(Math.round(position.x), 150, "should have scrolled right"); 120 | assert.equal(Math.round(position.y), 300, "should have scrolled down"); 121 | }); 122 | 123 | }); 124 | 125 | }); -------------------------------------------------------------------------------- /src/_q_element_list_test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. 2 | "use strict"; 3 | 4 | var assert = require("./util/assert.js"); 5 | var reset = require("./__reset.js"); 6 | var QElementList = require("./q_element_list.js"); 7 | 8 | describe("FOUNDATION: QElementList", function() { 9 | 10 | var none; 11 | var one; 12 | var some; 13 | var item1; 14 | var item2; 15 | var item3; 16 | 17 | before(function() { 18 | var frame = reset.frame; 19 | var document = frame.toDomElement().contentDocument; 20 | 21 | var list = frame.add("" + 22 | "", "list" 27 | ); 28 | 29 | var noneDom = document.querySelectorAll(".no-such-class"); 30 | var oneDom = document.querySelectorAll("ul"); 31 | var someDom = document.querySelectorAll("li"); 32 | 33 | none = new QElementList(noneDom, "none"); 34 | one = new QElementList(oneDom, "one"); 35 | some = new QElementList(someDom, "some"); 36 | 37 | item1 = frame.get("#item1"); 38 | item2 = frame.get("#item2"); 39 | item3 = frame.get("#item3"); 40 | }); 41 | 42 | it("reports length", function() { 43 | assert.equal(none.length(), 0, "none"); 44 | assert.equal(one.length(), 1, "one"); 45 | assert.equal(some.length(), 3, "some"); 46 | }); 47 | 48 | it("describes itself", function() { 49 | assert.equal(none.toString(), "'none' list"); 50 | }); 51 | 52 | it("retrieves elements by index", function() { 53 | assert.objEqual(some.at(0), item1, "zero-based indexing"); 54 | assert.objEqual(some.at(1), item2, "forward index"); 55 | assert.objEqual(some.at(-1), item3, "backward index"); 56 | }); 57 | 58 | it("describes retrieved elements", function() { 59 | assert.equal(some.at(1).toString(), "'some[1]'", "forward index"); 60 | assert.equal(some.at(-3).toString(), "'some[0]'", "backward index"); 61 | assert.equal(some.at(1, "my name").toString(), "'my name'", "nickname provided"); 62 | }); 63 | 64 | it("fails fast when element out of bounds", function() { 65 | assert.exception(function() { 66 | none.at(0); 67 | }, "'none'[0] is out of bounds; list length is 0", "forward index"); 68 | assert.exception(function() { 69 | some.at(-5); 70 | }, "'some'[-5] is out of bounds; list length is 3", "backward index"); 71 | }); 72 | 73 | }); -------------------------------------------------------------------------------- /src/_q_frame_test.css: -------------------------------------------------------------------------------- 1 | .style-me { 2 | font-size: 42px; 3 | -webkit-text-size-adjust: 100%; 4 | } -------------------------------------------------------------------------------- /src/_q_frame_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 |

    If this element exists, then this file was loaded into the frame.

    12 | 13 |

    If this element is styled, then the stylesheet was loaded using a link tag.

    14 | 15 | -------------------------------------------------------------------------------- /src/_q_frame_test2.css: -------------------------------------------------------------------------------- 1 | .style-me { 2 | height: 123px; 3 | } -------------------------------------------------------------------------------- /src/_q_page_test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. 2 | "use strict"; 3 | 4 | var assert = require("./util/assert.js"); 5 | var reset = require("./__reset.js"); 6 | var QPage = require("./q_page.js"); 7 | var Assertable = require("./assertable.js"); 8 | 9 | describe("FOUNDATION: QPage", function() { 10 | 11 | var WIDTH = reset.WIDTH + 200; 12 | var HEIGHT = reset.HEIGHT + 200; 13 | 14 | var frame; 15 | var page; 16 | 17 | beforeEach(function() { 18 | frame = reset.frame; 19 | page = new QPage(frame.toBrowsingContext()); 20 | 21 | frame.add( 22 | "
    element
    " 24 | ); 25 | }); 26 | 27 | it("is Assertable", function() { 28 | assert.implements(page, Assertable); 29 | }); 30 | 31 | it("has size properties", function() { 32 | assert.equal(page.width.diff(WIDTH), "", "width"); 33 | assert.equal(page.height.diff(HEIGHT), "", "height"); 34 | assert.equal(page.width.toString(), "width of page", "width description"); 35 | assert.equal(page.height.toString(), "height of page", "height description"); 36 | }); 37 | 38 | it("has edge properties", function() { 39 | assert.equal(page.top.diff(0), "", "top"); 40 | assert.equal(page.right.diff(WIDTH), "", "right"); 41 | assert.equal(page.bottom.diff(HEIGHT), "", "bottom"); 42 | assert.equal(page.left.diff(0), "", "left"); 43 | }); 44 | 45 | it("has center properties", function() { 46 | assert.equal(page.center.diff(WIDTH / 2), "", "center"); 47 | assert.equal(page.middle.diff(HEIGHT / 2), "", "middle"); 48 | assert.equal(page.center.toString(), "center of page", "center description"); 49 | assert.equal(page.middle.toString(), "middle of page", "middle description"); 50 | }); 51 | 52 | }); -------------------------------------------------------------------------------- /src/_q_viewport_test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. 2 | "use strict"; 3 | 4 | var assert = require("./util/assert.js"); 5 | var reset = require("./__reset.js"); 6 | var QViewport = require("./q_viewport.js"); 7 | var Assertable = require("./assertable.js"); 8 | 9 | describe("FOUNDATION: QViewport", function() { 10 | 11 | var frame; 12 | var viewport; 13 | 14 | beforeEach(function() { 15 | frame = reset.frame; 16 | viewport = new QViewport(frame.toBrowsingContext()); 17 | }); 18 | 19 | it("is Assertable", function() { 20 | assert.implements(viewport, Assertable); 21 | }); 22 | 23 | it("has size properties", function() { 24 | assert.equal(viewport.width.diff(reset.WIDTH), "", "width"); 25 | assert.equal(viewport.height.diff(reset.HEIGHT), "", "height"); 26 | assert.equal(viewport.width.toString(), "width of viewport", "width description"); 27 | assert.equal(viewport.height.toString(), "height of viewport", "height description"); 28 | }); 29 | 30 | it("has edge properties", function() { 31 | assert.equal(viewport.top.diff(0), "", "top"); 32 | assert.equal(viewport.right.diff(reset.WIDTH), "", "right"); 33 | assert.equal(viewport.bottom.diff(reset.HEIGHT), "", "bottom"); 34 | assert.equal(viewport.left.diff(0), "", "left"); 35 | }); 36 | 37 | it("has center properties", function() { 38 | assert.equal(viewport.center.diff(reset.WIDTH / 2), "", "center"); 39 | assert.equal(viewport.middle.diff(reset.HEIGHT / 2), "", "middle"); 40 | assert.equal(viewport.center.toString(), "center of viewport", "center description"); 41 | assert.equal(viewport.middle.toString(), "middle of viewport", "middle description"); 42 | }); 43 | 44 | }); -------------------------------------------------------------------------------- /src/_quixote_test.js: -------------------------------------------------------------------------------- 1 | // Copyright Titanium I.T. LLC. 2 | "use strict"; 3 | 4 | var assert = require("./util/assert.js"); 5 | var quixote = require("./quixote.js"); 6 | var QFrame = require("./q_frame.js"); 7 | var reset = require("./__reset.js"); 8 | 9 | describe("FOUNDATION: Quixote", function() { 10 | 11 | it("creates frame", function(done) { 12 | var frame = quixote.createFrame({ src: "/base/src/_q_frame_test.html" }, function(err, callbackFrame) { 13 | assert.noException(function() { 14 | callbackFrame.get("#exists"); 15 | }); 16 | frame.remove(); 17 | done(err); 18 | }); 19 | assert.type(frame, QFrame, "createFrame() returns frame object immediately"); 20 | }); 21 | 22 | it("creates QElement from DOM element", function() { 23 | var domElement = reset.frame.add("
    my element
    ").toDomElement(); 24 | 25 | var element = quixote.elementFromDom(domElement); 26 | assert.equal(element.toDomElement(), domElement); 27 | }); 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /src/assertable.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file. 2 | "use strict"; 3 | 4 | var ensure = require("./util/ensure.js"); 5 | var oop = require("./util/oop.js"); 6 | var shim = require("./util/shim.js"); 7 | 8 | var Me = module.exports = function Assertable() { 9 | ensure.unreachable("Assertable is abstract and should not be constructed directly."); 10 | }; 11 | Me.extend = oop.extendFn(Me); 12 | oop.makeAbstract(Me, []); 13 | 14 | Me.prototype.assert = function assert(expected, message) { 15 | ensure.signature(arguments, [ Object, [undefined, String] ]); 16 | if (message === undefined) message = "Differences found"; 17 | 18 | var diff = this.diff(expected); 19 | if (diff !== "") throw new Error(message + ":\n" + diff + "\n"); 20 | }; 21 | 22 | Me.prototype.diff = function diff(expected) { 23 | ensure.signature(arguments, [ Object ]); 24 | 25 | var result = []; 26 | var keys = shim.Object.keys(expected); 27 | var key, oneDiff, descriptor; 28 | for (var i = 0; i < keys.length; i++) { 29 | key = keys[i]; 30 | descriptor = this[key]; 31 | ensure.that( 32 | descriptor !== undefined, 33 | this + " doesn't have a property named '" + key + "'. Did you misspell it?" 34 | ); 35 | oneDiff = descriptor.diff(expected[key]); 36 | if (oneDiff !== "") result.push(oneDiff); 37 | } 38 | 39 | return result.join("\n"); 40 | }; 41 | -------------------------------------------------------------------------------- /src/browser.js: -------------------------------------------------------------------------------- 1 | // Copyright Titanium I.T. LLC. 2 | "use strict"; 3 | 4 | var ensure = require("./util/ensure.js"); 5 | var QFrame = require("./q_frame.js"); 6 | var Size = require("./values/size.js"); 7 | 8 | var FRAME_WIDTH = 1500; 9 | var FRAME_HEIGHT = 200; 10 | 11 | var features = null; 12 | 13 | exports.enlargesFrameToPageSize = createDetectionMethod("enlargesFrame"); 14 | exports.enlargesFonts = createDetectionMethod("enlargesFonts"); 15 | exports.misreportsClipAutoProperty = createDetectionMethod("misreportsClipAuto"); 16 | exports.misreportsAutoValuesInClipProperty = createDetectionMethod("misreportsClipValues"); 17 | exports.roundsOffPixelCalculations = createDetectionMethod("roundsOffPixelCalculations"); 18 | 19 | exports.detectBrowserFeatures = function(callback) { 20 | var frame = QFrame.create(document.body, { width: FRAME_WIDTH, height: FRAME_HEIGHT }, function(err) { 21 | if (err) { 22 | return callback(new Error("Error while creating Quixote browser feature detection frame: " + err)); 23 | } 24 | return detectFeatures(frame, function(err) { 25 | frame.remove(); 26 | return callback(err); 27 | }); 28 | }); 29 | }; 30 | 31 | function detectFeatures(frame, callback) { 32 | try { 33 | features = {}; 34 | features.enlargesFrame = detectFrameEnlargement(frame, FRAME_WIDTH); 35 | features.misreportsClipAuto = detectReportedClipAuto(frame); 36 | features.misreportsClipValues = detectReportedClipPropertyValues(frame); 37 | features.roundsOffPixelCalculations = detectRoundsOffPixelCalculations(frame); 38 | 39 | detectFontEnlargement(frame, FRAME_WIDTH, function(result) { 40 | features.enlargesFonts = result; 41 | frame.remove(); 42 | return callback(null); 43 | }); 44 | 45 | } 46 | catch(err) { 47 | features = null; 48 | return callback(new Error("Error during Quixote browser feature detection: " + err)); 49 | } 50 | } 51 | 52 | function createDetectionMethod(propertyName) { 53 | return function() { 54 | ensure.signature(arguments, []); 55 | ensure.that( 56 | features !== null, 57 | "Must call quixote.createFrame() before using Quixote browser feature detection." 58 | ); 59 | 60 | return features[propertyName]; 61 | }; 62 | } 63 | 64 | function detectFrameEnlargement(frame, frameWidth) { 65 | frame.reset(); 66 | 67 | frame.add("
    force scrolling
    "); 68 | return !frame.viewport().width.value().equals(Size.create(frameWidth)); 69 | } 70 | 71 | function detectReportedClipAuto(frame) { 72 | frame.reset(); 73 | 74 | var element = frame.add("
    "); 75 | var clip = element.getRawStyle("clip"); 76 | 77 | return clip !== "auto"; 78 | } 79 | 80 | function detectReportedClipPropertyValues(frame) { 81 | frame.reset(); 82 | 83 | var element = frame.add("
    "); 84 | var clip = element.getRawStyle("clip"); 85 | 86 | // WORKAROUND IE 8: Provides 'clipTop' etc. instead of 'clip' property 87 | if (clip === "" && element.getRawStyle("clip-top") === "auto") return false; 88 | 89 | return clip !== "rect(auto, auto, auto, auto)" && clip !== "rect(auto auto auto auto)"; 90 | } 91 | 92 | function detectRoundsOffPixelCalculations(frame) { 93 | var element = frame.add("
    "); 94 | var size = element.calculatePixelValue("0.5em"); 95 | 96 | if (size === 7.5) return false; 97 | if (size === 8) return true; 98 | ensure.unreachable("Failure in roundsOffPixelValues() detection: expected 7.5 or 8, but got " + size); 99 | } 100 | 101 | function detectFontEnlargement(frame, frameWidth, callback) { 102 | ensure.that(frameWidth >= 1500, "Detector frame width must be larger than screen to detect font enlargement"); 103 | frame.reset(); 104 | 105 | // WORKAROUND IE 8: we use a
    because the
    "); 108 | 109 | var text = frame.add("

    arbitrary text

    "); 110 | frame.add("

    must have two p tags to work

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