├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.js ├── .template-lintrc.js ├── .tool-versions ├── .watchmanconfig ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── addon ├── breakpoint.js ├── index.js ├── services │ └── screen.js └── window │ ├── browser.js │ └── null.js ├── app ├── .gitkeep └── services │ └── screen.js ├── config ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── fastboot-tests ├── fixtures │ └── fastboot │ │ └── app │ │ ├── components │ │ └── es-display.js │ │ ├── instance-initializers │ │ └── fastboot │ │ │ └── stub-media.js │ │ ├── router.js │ │ └── templates │ │ ├── components │ │ └── es-display.hbs │ │ └── index.hbs └── index-test.js ├── index.js ├── package-lock.json ├── package.json ├── testem.js ├── tests ├── acceptance │ ├── acceptance-example-test.js │ └── resizing-test.js ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ └── es-display.js │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ └── components │ │ │ └── es-display.hbs │ ├── config │ │ ├── ember-cli-update.json │ │ ├── environment.js │ │ ├── optional-features.json │ │ └── targets.js │ └── public │ │ └── robots.txt ├── helpers │ ├── .gitkeep │ └── wait-for-width.js ├── index.html ├── integration │ └── .gitkeep ├── test-helper.js └── unit │ ├── .gitkeep │ └── services │ ├── force-media-features-test.js │ └── screen-test.js └── vendor ├── .gitkeep └── shims └── css-mediaquery.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /package.json.ember-try 23 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | ecmaVersion: 2018, 8 | sourceType: 'module', 9 | ecmaFeatures: { 10 | legacyDecorators: true, 11 | }, 12 | }, 13 | plugins: ['ember'], 14 | extends: [ 15 | 'eslint:recommended', 16 | 'plugin:ember/recommended', 17 | 'plugin:prettier/recommended', 18 | ], 19 | env: { 20 | browser: true, 21 | }, 22 | rules: {}, 23 | overrides: [ 24 | // node files 25 | { 26 | files: [ 27 | './.eslintrc.js', 28 | './.prettierrc.js', 29 | './.template-lintrc.js', 30 | './ember-cli-build.js', 31 | './index.js', 32 | './testem.js', 33 | './blueprints/*/index.js', 34 | './config/**/*.js', 35 | './tests/dummy/config/**/*.js', 36 | ], 37 | parserOptions: { 38 | sourceType: 'script', 39 | }, 40 | env: { 41 | browser: false, 42 | node: true, 43 | }, 44 | plugins: ['node'], 45 | extends: ['plugin:node/recommended'], 46 | }, 47 | { 48 | // Test files: 49 | files: ['tests/**/*-test.{js,ts}'], 50 | extends: ['plugin:qunit/recommended'], 51 | }, 52 | ], 53 | }; 54 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | # filtering branches here prevents duplicate builds from pull_request and push 7 | branches: 8 | - master 9 | schedule: 10 | - cron: '0 3 * * 0' # every Sunday at 3am 11 | 12 | env: 13 | CI: true 14 | 15 | jobs: 16 | tests: 17 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 18 | name: Base Tests 19 | timeout-minutes: 5 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v2 23 | with: 24 | persist-credentials: false 25 | - name: Reconfigure git to use HTTP authentication 26 | run: > 27 | git config --global url."https://github.com/".insteadOf 28 | ssh://git@github.com/ 29 | - name: Install according to lock file 30 | run: npm ci 31 | - name: Install xvfb for running headful Chrome 32 | run: sudo apt-get install xvfb 33 | - name: Lint 34 | run: npm run lint 35 | - name: Run Tests 36 | run: xvfb-run --auto-servernum npm test 37 | 38 | try-scenarios: 39 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 40 | name: "Compatibility" 41 | timeout-minutes: 10 42 | runs-on: ubuntu-latest 43 | needs: tests 44 | 45 | strategy: 46 | fail-fast: true 47 | matrix: 48 | ember-try-scenario: 49 | - ember-lts-3.20 50 | - ember-lts-3.24 51 | - ember-release 52 | - ember-beta 53 | # - embroider 54 | steps: 55 | - uses: actions/checkout@v2 56 | with: 57 | persist-credentials: false 58 | - name: Reconfigure git to use HTTP authentication 59 | run: > 60 | git config --global url."https://github.com/".insteadOf 61 | ssh://git@github.com/ 62 | - name: Install dependencies 63 | run: npm install 64 | - name: test 65 | run: xvfb-run --auto-servernum node_modules/.bin/ember try:one ${{ matrix.ember-try-scenario }} --skip-cleanup 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /.eslintcache 16 | /connect.lock 17 | /coverage/ 18 | /libpeerconnection.log 19 | /npm-debug.log* 20 | /testem.log 21 | 22 | # ember-try 23 | /.node_modules.ember-try/ 24 | /bower.json.ember-try 25 | /package.json.ember-try 26 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.env* 13 | /.eslintcache 14 | /.eslintignore 15 | /.eslintrc.js 16 | /.git/ 17 | /.gitignore 18 | /.prettierignore 19 | /.prettierrc.js 20 | /.template-lintrc.js 21 | /.watchmanconfig 22 | /bower.json 23 | /config/ember-try.js 24 | /CONTRIBUTING.md 25 | /ember-cli-build.js 26 | /testem.js 27 | /tests/ 28 | /fastboot-tests/ 29 | /package-lock.json 30 | .gitkeep 31 | 32 | # CI 33 | /.github/ 34 | .tool-versions 35 | 36 | # ember-try 37 | /.node_modules.ember-try/ 38 | /bower.json.ember-try 39 | /package.json.ember-try 40 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | 18 | # ember-try 19 | /.node_modules.ember-try/ 20 | /bower.json.ember-try 21 | /package.json.ember-try 22 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | }; 6 | -------------------------------------------------------------------------------- /.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 12.22.7 2 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | * `git clone ` 6 | * `cd ember-screen` 7 | * `npm install` 8 | 9 | ## Linting 10 | 11 | * `npm run lint` 12 | * `npm run lint:fix` 13 | 14 | ## Running tests 15 | 16 | * `ember test` – Runs the test suite on the current Ember version 17 | * `ember test --server` – Runs the test suite in "watch mode" 18 | * `ember try:each` – Runs the test suite against multiple Ember versions 19 | 20 | ## Running the dummy application 21 | 22 | * `ember serve` 23 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200). 24 | 25 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember Screen 2 | 3 | [ ![Codeship Status for mitchlloyd/ember-screen](https://codeship.com/projects/efc09170-87ef-0133-2329-32f8e6acffcd/status?branch=master)](https://codeship.com/projects/123088) 4 | 5 | This addon adds a `screen` service to your Ember application that will report 6 | the current height and width of the window. 7 | 8 | ```javascript 9 | import Component from '@glimmer/component'; 10 | import { service } from '@ember/service'; 11 | 12 | export default class ExampleComponent extends Component { 13 | @service screen; 14 | 15 | get showTopNavigation() { 16 | return this.screen.width > 1_000; 17 | } 18 | } 19 | ``` 20 | 21 | ## Installation 22 | 23 | ``` 24 | ember install ember-screen 25 | ``` 26 | 27 | ## Using Media Queries 28 | 29 | Ember Screen is configured with a set of properties that correspond to 30 | [Bootstrap 4's media queries](http://v4-alpha.getbootstrap.com/layout/overview/#responsive-breakpoints). 31 | This is the default implementation of `app/services/screen.js`. 32 | 33 | ```javascript 34 | import EmberScreen, { breakpoint } from 'ember-screen'; 35 | 36 | export default class ScreenService extends EmberScreen { 37 | @breakpoint('(min-width: 34em)') isSmallAndUp; 38 | @breakpoint('(min-width: 48em)') isMediumAndUp; 39 | @breakpoint('(min-width: 62em)') isLargeAndUp; 40 | @breakpoint('(min-width: 75em)') isExtraLargeAndUp; 41 | 42 | @breakpoint('(max-width: 33.9999em)') isExtraSmallAndDown; 43 | @breakpoint('(max-width: 47.9999em)') isSmallAndDown; 44 | @breakpoint('(max-width: 61.9999em)') isMediumAndDown; 45 | @breakpoint('(max-width: 74.9999em') isLargeAndDown; 46 | } 47 | ``` 48 | 49 | If you inject `screen` into a component, you could use a media query property 50 | like this: 51 | 52 | ```handlebars 53 | {{#if this.screen.isSmallAndDown}} 54 | ☰ 55 | {{/if}} 56 | ``` 57 | 58 | To configure your own media queries, create an `app/services/screen.js` file 59 | in your application and extend from the Ember Screen service. 60 | 61 | ```javascript 62 | import EmberScreen, { breakpoint } from 'ember-screen'; 63 | 64 | export default class ScreenService extends EmberScreen { 65 | @breakpoint('(max-width: 479px)') isMobile; 66 | @breakpoint('(min-width: 480px)') isDesktop; 67 | } 68 | ``` 69 | 70 | ## Testing Media Queries 71 | 72 | Creating automated tests for different screen sizes is often neglected because 73 | it is not practical to programmatically resize a web browser during tests. Ember 74 | Screen lets you to stub [media features](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#Media_features) 75 | to run tests that are integrated with your screen service logic. 76 | 77 | ```javascript 78 | // An example acceptance test 79 | 80 | test('shows large logo on HD tv', async function(assert) { 81 | let screen = this.owner.lookup('service:screen'); 82 | screen.stubMediaFeatures({ type: 'tv', width: 1920 }); 83 | 84 | await visit('/'); 85 | 86 | assert.dom('.logo-large').exists('HD TVs have large logo'); 87 | }); 88 | ``` 89 | 90 | This feature uses [css-mediaquery](https://github.com/ericf/css-mediaquery) to 91 | parse your configured `breakpoints` and see if they match with the stubbed 92 | values. 93 | 94 | ## Running in FastBoot 95 | 96 | Ember screen is compatible with [FastBoot](https://ember-fastboot.com) out 97 | of the box. However in a FastBoot environment running on a server there is 98 | no way to access the screen properties of the client. If your UI depends on 99 | the device's screen size then you can use the screen service's 100 | `stubMediaFeatures` method to provide defaults. See this [simple 101 | example](fastboot-tests/fixtures/fastboot/app/instance-initializers/fastboot/stub-media.js) 102 | of a FastBoot-only instance initializer. 103 | 104 | ## Running the Tests for this Addon 105 | 106 | Running tests that resize a web browser is challenging because web browsers 107 | disable `window.resizeTo` and `window.resizeBy` APIs. Chrome will allow these 108 | methods to work for popup windows that have been programatically created with 109 | `window.open`. The current test suite has some finicky configuration will only 110 | succeed when running with Testem, so the proper way to run tests locally is to 111 | use: 112 | 113 | ``` 114 | ember test 115 | ``` 116 | 117 | or 118 | 119 | ``` 120 | ember test --server 121 | ``` 122 | 123 | -------------------------------------------------------------------------------- /addon/breakpoint.js: -------------------------------------------------------------------------------- 1 | import { computed } from '@ember/object'; 2 | 3 | export default function breakpoint(mediaQuery) { 4 | return computed('width', 'win', function () { 5 | return this.win.matchesMediaQuery(mediaQuery); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /addon/index.js: -------------------------------------------------------------------------------- 1 | import ScreenService from 'ember-screen/services/screen'; 2 | import breakpoint from 'ember-screen/breakpoint'; 3 | 4 | export default ScreenService; 5 | export { breakpoint }; 6 | -------------------------------------------------------------------------------- /addon/services/screen.js: -------------------------------------------------------------------------------- 1 | /* globals FastBoot */ 2 | import Service from '@ember/service'; 3 | import BrowserWindow from 'ember-screen/window/browser'; 4 | import NullWindow from 'ember-screen/window/null'; 5 | import { tracked } from '@glimmer/tracking'; 6 | 7 | const isFastBoot = typeof FastBoot !== 'undefined'; 8 | const WindowClass = isFastBoot ? NullWindow : BrowserWindow; 9 | 10 | export default class ScreenService extends Service { 11 | @tracked width; 12 | @tracked height; 13 | 14 | constructor() { 15 | super(...arguments); 16 | this.win = new WindowClass(); 17 | this.win.onSizeUpdate(this.handleResize.bind(this)); 18 | } 19 | 20 | willDestroy() { 21 | this.win.teardown(); 22 | } 23 | 24 | handleResize({ width, height }) { 25 | this.width = width; 26 | this.height = height; 27 | } 28 | 29 | stubMediaFeatures(features) { 30 | this.win.stubMediaFeatures(features); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /addon/window/browser.js: -------------------------------------------------------------------------------- 1 | import { matchQuery } from 'css-mediaquery'; 2 | 3 | export default class { 4 | constructor() { 5 | this.listeners = []; 6 | this.resizeListener = this._windowDidResize.bind(this); 7 | this.stubbedMediaFeatures = false; 8 | window.addEventListener('resize', this.resizeListener); 9 | } 10 | 11 | onSizeUpdate(listener) { 12 | // Immediately call the listener to set initial size 13 | listener(this.dimensions); 14 | 15 | this.listeners.push(listener); 16 | } 17 | 18 | get dimensions() { 19 | if (this.stubbedMediaFeatures) { 20 | return { 21 | width: this.stubbedMediaFeatures.width || window.innerWidth, 22 | height: this.stubbedMediaFeatures.height || window.innerHeight, 23 | }; 24 | } else { 25 | return { 26 | width: window.innerWidth, 27 | height: window.innerHeight, 28 | }; 29 | } 30 | } 31 | 32 | stubMediaFeatures(features) { 33 | this.stubbedMediaFeatures = features; 34 | this.resizeListener(); 35 | } 36 | 37 | teardown() { 38 | window.removeEventListener('resize', this.resizeListener); 39 | } 40 | 41 | matchesMediaQuery(query) { 42 | if (this.stubbedMediaFeatures) { 43 | return matchQuery(query, this.stubbedMediaFeatures); 44 | } else { 45 | return window.matchMedia(query).matches; 46 | } 47 | } 48 | 49 | _windowDidResize() { 50 | this.listeners.forEach((l) => l(this.dimensions)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /addon/window/null.js: -------------------------------------------------------------------------------- 1 | import { matchQuery } from 'css-mediaquery'; 2 | 3 | const MOST_COMMON_SCREEN_SIZE = { 4 | width: 1366, 5 | height: 768, 6 | type: 'screen', 7 | }; 8 | 9 | export default class { 10 | constructor() { 11 | this.listeners = []; 12 | this.stubbedMediaFeatures = MOST_COMMON_SCREEN_SIZE; 13 | } 14 | 15 | onSizeUpdate(listener) { 16 | // Immediately call the listener to set initial size 17 | listener(this.dimensions); 18 | 19 | this.listeners.push(listener); 20 | } 21 | 22 | get dimensions() { 23 | return { 24 | width: this.stubbedMediaFeatures.width, 25 | height: this.stubbedMediaFeatures.height, 26 | }; 27 | } 28 | 29 | stubMediaFeatures(features) { 30 | this.stubbedMediaFeatures = features; 31 | this.listeners.forEach((l) => l(this.dimensions)); 32 | } 33 | 34 | teardown() {} 35 | 36 | matchesMediaQuery(query) { 37 | return matchQuery(query, this.stubbedMediaFeatures); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchlloyd/ember-screen/1a77350f6ca6a70fdd4eb0a7dc2191c46616fc7c/app/.gitkeep -------------------------------------------------------------------------------- /app/services/screen.js: -------------------------------------------------------------------------------- 1 | import EmberScreen, { breakpoint } from 'ember-screen'; 2 | 3 | export default class ScreenService extends EmberScreen { 4 | @breakpoint('(min-width: 34em)') isSmallAndUp; 5 | @breakpoint('(min-width: 48em)') isMediumAndUp; 6 | @breakpoint('(min-width: 62em)') isLargeAndUp; 7 | @breakpoint('(min-width: 75em)') isExtraLargeAndUp; 8 | 9 | @breakpoint('(max-width: 33.9999em)') isExtraSmallAndDown; 10 | @breakpoint('(max-width: 47.9999em)') isSmallAndDown; 11 | @breakpoint('(max-width: 61.9999em)') isMediumAndDown; 12 | @breakpoint('(max-width: 74.9999em)') isLargeAndDown; 13 | } 14 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 5 | 6 | module.exports = async function () { 7 | return { 8 | scenarios: [ 9 | { 10 | name: 'ember-lts-3.20', 11 | npm: { 12 | devDependencies: { 13 | 'ember-source': '~3.20.5', 14 | }, 15 | }, 16 | }, 17 | { 18 | name: 'ember-lts-3.24', 19 | npm: { 20 | devDependencies: { 21 | 'ember-source': '~3.24.3', 22 | }, 23 | }, 24 | }, 25 | { 26 | name: 'ember-release', 27 | npm: { 28 | devDependencies: { 29 | 'ember-source': await getChannelURL('release'), 30 | }, 31 | }, 32 | }, 33 | { 34 | name: 'ember-beta', 35 | npm: { 36 | devDependencies: { 37 | 'ember-source': await getChannelURL('beta'), 38 | }, 39 | }, 40 | }, 41 | { 42 | name: 'ember-classic', 43 | env: { 44 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 45 | 'application-template-wrapper': true, 46 | 'default-async-observers': false, 47 | 'template-only-glimmer-components': false, 48 | }), 49 | }, 50 | npm: { 51 | devDependencies: { 52 | 'ember-source': '~3.28.0', 53 | }, 54 | ember: { 55 | edition: 'classic', 56 | }, 57 | }, 58 | }, 59 | embroiderSafe(), 60 | embroiderOptimized(), 61 | ], 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (/* environment, appConfig */) { 4 | return {}; 5 | }; 6 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function (defaults) { 6 | let app = new EmberAddon(defaults, { 7 | // Add options here 8 | }); 9 | 10 | /* 11 | This build file specifies the options for the dummy test app of this 12 | addon, located in `/tests/dummy` 13 | This build file does *not* influence how the addon or the app using it 14 | behave. You most likely want to be modifying `./index.js` or app's build file 15 | */ 16 | 17 | const { maybeEmbroider } = require('@embroider/test-setup'); 18 | return maybeEmbroider(app, { 19 | skipBabel: [ 20 | { 21 | package: 'qunit', 22 | }, 23 | ], 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /fastboot-tests/fixtures/fastboot/app/components/es-display.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Component from '@glimmer/component'; 3 | const { inject } = Ember; 4 | 5 | export default class EsDisplay extends Component { 6 | screen = inject.service(); 7 | } 8 | -------------------------------------------------------------------------------- /fastboot-tests/fixtures/fastboot/app/instance-initializers/fastboot/stub-media.js: -------------------------------------------------------------------------------- 1 | export function initialize(appInstance) { 2 | let screen = appInstance.lookup('service:screen'); 3 | screen.stubMediaFeatures({ width: 900 }); 4 | } 5 | 6 | export default { 7 | name: 'stub-media', 8 | initialize, 9 | }; 10 | -------------------------------------------------------------------------------- /fastboot-tests/fixtures/fastboot/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | 11 | export default Router; 12 | -------------------------------------------------------------------------------- /fastboot-tests/fixtures/fastboot/app/templates/components/es-display.hbs: -------------------------------------------------------------------------------- 1 |
2 |
width
3 |
{{this.screen.width}}
4 |
height
5 |
{{this.screen.height}}
6 |
7 | 8 |
9 |
isSmallAndUp
10 |
{{this.screen.isSmallAndUp}}
11 | 12 |
isMediumAndUp
13 |
{{this.screen.isMediumAndUp}}
14 | 15 |
isLargeAndUp
16 |
{{this.screen.isLargeAndUp}}
17 | 18 |
isExtraLargeAndUp
19 |
{{this.screen.isExtraLargeAndUp}}
20 | 21 |
22 | 23 |
isExtraSmallAndDown
24 |
{{this.screen.isExtraSmallAndDown}}
25 | 26 |
isSmallAndDown
27 |
{{this.screen.isSmallAndDown}}
28 | 29 |
isMediumAndDown
30 |
{{this.screen.isMediumAndDown}}
31 | 32 |
isLargeAndDown
33 |
{{this.screen.isLargeAndDown}}
34 |
35 | -------------------------------------------------------------------------------- /fastboot-tests/fixtures/fastboot/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 |

ember-fastboot-addon-tests

2 | 3 | 4 | -------------------------------------------------------------------------------- /fastboot-tests/index-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*jshint node:true*/ 3 | /* global describe, it, require */ 4 | const expect = require('chai').expect; 5 | const setupTest = require('ember-fastboot-addon-tests').setupTest; 6 | 7 | function serializeMediaQueries($) { 8 | let data = {}; 9 | 10 | $('#media-queries dt') 11 | .toArray() 12 | .forEach(function (dt) { 13 | data[$(dt).text().trim()] = $(dt).next().text().trim(); 14 | }); 15 | 16 | return data; 17 | } 18 | 19 | describe('index', function () { 20 | setupTest(); 21 | 22 | it('renders', function () { 23 | return this.visit('/').then(function (res) { 24 | let $ = res.jQuery; 25 | expect($('body').length).to.equal(1); 26 | expect($('h1').text().trim()).to.equal('ember-fastboot-addon-tests'); 27 | expect($('#dimensions').length).to.equal(1); 28 | expect($('#media-queries').length).to.equal(1); 29 | }); 30 | }); 31 | 32 | it('can stub media features', function () { 33 | return this.visit('/').then(function (res) { 34 | let $ = res.jQuery; 35 | expect($('#width').text().trim()).to.equal('900'); // see fixtures/fastboot/app/instance-initializers/fastboot/stub-media.js 36 | expect(serializeMediaQueries($)).to.deep.equal({ 37 | isSmallAndUp: 'true', 38 | isMediumAndUp: 'true', 39 | isLargeAndUp: 'false', 40 | isExtraLargeAndUp: 'false', 41 | 42 | isExtraSmallAndDown: 'false', 43 | isSmallAndDown: 'false', 44 | isMediumAndDown: 'true', 45 | isLargeAndDown: 'true', 46 | }); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: require('./package').name, 5 | 6 | included: function (app) { 7 | this._super.included(app); 8 | 9 | app.import('vendor/shims/css-mediaquery.js'); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-screen", 3 | "version": "2.0.0", 4 | "description": "A screen size service for Ember", 5 | "scripts": { 6 | "build": "ember build --environment=production", 7 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", 8 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", 9 | "lint:hbs": "ember-template-lint .", 10 | "lint:hbs:fix": "ember-template-lint . --fix", 11 | "lint:js": "eslint . --cache", 12 | "lint:js:fix": "eslint . --fix", 13 | "start": "ember serve", 14 | "test": "ember test" 15 | }, 16 | "dependencies": { 17 | "ember-cli-babel": "^7.26.6", 18 | "ember-cli-htmlbars": "^5.7.1" 19 | }, 20 | "devDependencies": { 21 | "@ember/optional-features": "^2.0.0", 22 | "@ember/test-helpers": "^2.4.2", 23 | "@embroider/test-setup": "^0.43.5", 24 | "@glimmer/component": "^1.0.4", 25 | "@glimmer/tracking": "^1.0.4", 26 | "babel-eslint": "^10.1.0", 27 | "broccoli-asset-rev": "^3.0.0", 28 | "chai": "^3.5.0", 29 | "ember-auto-import": "^2.2.4", 30 | "ember-cli": "~3.28.4", 31 | "ember-cli-dependency-checker": "^3.2.0", 32 | "ember-cli-inject-live-reload": "^2.1.0", 33 | "ember-cli-sri": "^2.1.1", 34 | "ember-cli-terser": "^4.0.2", 35 | "ember-disable-prototype-extensions": "^1.1.3", 36 | "ember-export-application-global": "^2.0.1", 37 | "ember-fastboot-addon-tests": "0.4.0", 38 | "ember-load-initializers": "^2.1.2", 39 | "ember-maybe-import-regenerator": "^0.1.6", 40 | "ember-page-title": "^6.2.2", 41 | "ember-qunit": "^5.1.4", 42 | "ember-resolver": "^8.0.2", 43 | "ember-source": "~3.28.0", 44 | "ember-source-channel-url": "^3.0.0", 45 | "ember-template-lint": "^3.6.0", 46 | "ember-try": "^1.4.0", 47 | "eslint": "^7.32.0", 48 | "eslint-config-prettier": "^8.3.0", 49 | "eslint-plugin-ember": "^10.5.4", 50 | "eslint-plugin-node": "^11.1.0", 51 | "eslint-plugin-prettier": "^3.4.1", 52 | "eslint-plugin-qunit": "^6.2.0", 53 | "loader.js": "^4.7.0", 54 | "npm-run-all": "^4.1.5", 55 | "prettier": "^2.3.2", 56 | "qunit": "^2.16.0", 57 | "qunit-dom": "^1.6.0", 58 | "webpack": "^5.64.4" 59 | }, 60 | "engines": { 61 | "node": "12.* || 14.* || >= 16" 62 | }, 63 | "keywords": [ 64 | "ember-addon" 65 | ], 66 | "repository": "https://github.com/mitchlloyd/ember-screen", 67 | "license": "MIT", 68 | "author": "", 69 | "directories": { 70 | "doc": "doc", 71 | "test": "tests" 72 | }, 73 | "ember": { 74 | "edition": "octane" 75 | }, 76 | "ember-addon": { 77 | "configPath": "tests/dummy/config" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--disable-dev-shm-usage', 15 | '--disable-popup-blocking', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | dev: [ 22 | '--disable-popup-blocking', 23 | '--remote-debugging-port=0', 24 | '--window-size=1440,900', 25 | ], 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /tests/acceptance/acceptance-example-test.js: -------------------------------------------------------------------------------- 1 | import { visit } from '@ember/test-helpers'; 2 | import { module, test } from 'qunit'; 3 | import { setupApplicationTest } from 'ember-qunit'; 4 | 5 | module('Acceptance | temp', function (hooks) { 6 | setupApplicationTest(hooks); 7 | 8 | test('forcing media features in an acceptance test', async function (assert) { 9 | let screen = this.owner.lookup('service:screen'); 10 | screen.stubMediaFeatures({ width: '1px' }); 11 | 12 | await visit('/'); 13 | 14 | assert.ok(screen.get('isExtraSmallAndDown'), 'Media is matched'); 15 | 16 | screen.stubMediaFeatures({ width: '1000px' }); 17 | 18 | assert.notOk(screen.get('isExtraSmallAndDown'), 'Media is not matched'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/acceptance/resizing-test.js: -------------------------------------------------------------------------------- 1 | import { module, test, skip } from 'qunit'; 2 | import { setupApplicationTest } from 'ember-qunit'; 3 | import waitForWidth from 'dummy/tests/helpers/wait-for-width'; 4 | 5 | const HEIGHT = 500; 6 | const isHeadlessChrome = /HeadlessChrome/.test(window.navigator.userAgent); 7 | 8 | module('Acceptance | resizing', function (hooks) { 9 | setupApplicationTest(hooks); 10 | 11 | hooks.beforeEach(function () { 12 | this.popup = window.open('/index.html', 'resizable', `resizable=yes`); 13 | this.popup.addEventListener('load', (event) => { 14 | event.currentTarget.DummyApplication.visit('/'); 15 | }); 16 | }); 17 | 18 | hooks.afterEach(function () { 19 | this.popup.close(); 20 | }); 21 | 22 | if (isHeadlessChrome) { 23 | skip('Skipping Chrome resizing integration test'); 24 | return; 25 | } 26 | 27 | test('visiting /resizing', async function (assert) { 28 | this.popup.resizeTo(200, HEIGHT); 29 | await waitForWidth(this.popup, 200); 30 | 31 | assert.deepEqual( 32 | serializeMediaQueries(this.popup.document), 33 | { 34 | isSmallAndUp: 'false', 35 | isMediumAndUp: 'false', 36 | isLargeAndUp: 'false', 37 | isExtraLargeAndUp: 'false', 38 | 39 | isExtraSmallAndDown: 'true', 40 | isSmallAndDown: 'true', 41 | isMediumAndDown: 'true', 42 | isLargeAndDown: 'true', 43 | }, 44 | 'Initial values are correct' 45 | ); 46 | 47 | this.popup.resizeTo(900, HEIGHT); 48 | await waitForWidth(this.popup, 900); 49 | 50 | assert.deepEqual( 51 | serializeMediaQueries(this.popup.document), 52 | { 53 | isSmallAndUp: 'true', 54 | isMediumAndUp: 'true', 55 | isLargeAndUp: 'false', 56 | isExtraLargeAndUp: 'false', 57 | 58 | isExtraSmallAndDown: 'false', 59 | isSmallAndDown: 'false', 60 | isMediumAndDown: 'true', 61 | isLargeAndDown: 'true', 62 | }, 63 | 'Updated values are correct' 64 | ); 65 | }); 66 | 67 | function serializeMediaQueries(doc) { 68 | let data = {}; 69 | 70 | doc.querySelectorAll('#media-queries dt').forEach(function (dt) { 71 | data[dt.textContent.trim()] = dt.nextElementSibling.textContent.trim(); 72 | }); 73 | 74 | return data; 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'dummy/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | 11 | constructor(...args) { 12 | super(...args); 13 | 14 | // Make the application available so that we can call .visit('/') on it in a 15 | // popup. 16 | window.DummyApplication = this; 17 | } 18 | } 19 | 20 | loadInitializers(App, config.modulePrefix); 21 | -------------------------------------------------------------------------------- /tests/dummy/app/components/es-display.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class EsDisplay extends Component { 5 | @service screen; 6 | } 7 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchlloyd/ember-screen/1a77350f6ca6a70fdd4eb0a7dc2191c46616fc7c/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchlloyd/ember-screen/1a77350f6ca6a70fdd4eb0a7dc2191c46616fc7c/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | {{content-for "body-footer"}} 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchlloyd/ember-screen/1a77350f6ca6a70fdd4eb0a7dc2191c46616fc7c/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'dummy/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchlloyd/ember-screen/1a77350f6ca6a70fdd4eb0a7dc2191c46616fc7c/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchlloyd/ember-screen/1a77350f6ca6a70fdd4eb0a7dc2191c46616fc7c/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/es-display.hbs: -------------------------------------------------------------------------------- 1 |
2 |
width
3 |
{{this.screen.width}}
4 |
height
5 |
{{this.screen.height}}
6 |
7 | 8 |
9 |
isSmallAndUp
10 |
{{this.screen.isSmallAndUp}}
11 | 12 |
isMediumAndUp
13 |
{{this.screen.isMediumAndUp}}
14 | 15 |
isLargeAndUp
16 |
{{this.screen.isLargeAndUp}}
17 | 18 |
isExtraLargeAndUp
19 |
{{this.screen.isExtraLargeAndUp}}
20 | 21 |
22 | 23 |
isExtraSmallAndDown
24 |
{{this.screen.isExtraSmallAndDown}}
25 | 26 |
isSmallAndDown
27 |
{{this.screen.isSmallAndDown}}
28 | 29 |
isMediumAndDown
30 |
{{this.screen.isMediumAndDown}}
31 | 32 |
isLargeAndDown
33 |
{{this.screen.isLargeAndDown}}
34 |
35 | -------------------------------------------------------------------------------- /tests/dummy/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "3.28.4", 7 | "blueprints": [ 8 | { 9 | "name": "addon", 10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output", 11 | "codemodsSource": "ember-addon-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--no-welcome" 15 | ] 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | let ENV = { 5 | modulePrefix: 'dummy', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false, 17 | }, 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | }, 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /tests/dummy/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | // Ember's browser support policy is changing, and IE11 support will end in 10 | // v4.0 onwards. 11 | // 12 | // See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy 13 | // 14 | // If you need IE11 support on a version of Ember that still offers support 15 | // for it, uncomment the code block below. 16 | // 17 | // const isCI = Boolean(process.env.CI); 18 | // const isProduction = process.env.EMBER_ENV === 'production'; 19 | // 20 | // if (isCI || isProduction) { 21 | // browsers.push('ie 11'); 22 | // } 23 | 24 | module.exports = { 25 | browsers, 26 | }; 27 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchlloyd/ember-screen/1a77350f6ca6a70fdd4eb0a7dc2191c46616fc7c/tests/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/helpers/wait-for-width.js: -------------------------------------------------------------------------------- 1 | import { settled } from '@ember/test-helpers'; 2 | import { later } from '@ember/runloop'; 3 | 4 | export default async function (_window, expectedWidth) { 5 | let tries = 0; 6 | 7 | let poll = function () { 8 | let doc = _window.document; 9 | const width = Number(doc.getElementById('width')?.textContent.trim()); 10 | 11 | if (width === expectedWidth) { 12 | return width; 13 | } 14 | 15 | if (tries > 100) { 16 | throw new Error(`Width never became ${expectedWidth}. It was ${width}`); 17 | } else { 18 | tries = tries + 1; 19 | later(poll, 10); 20 | } 21 | }; 22 | 23 | poll(); 24 | 25 | return settled(); 26 | } 27 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{content-for "body-footer"}} 38 | {{content-for "test-body-footer"}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchlloyd/ember-screen/1a77350f6ca6a70fdd4eb0a7dc2191c46616fc7c/tests/integration/.gitkeep -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'dummy/app'; 2 | import config from 'dummy/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setApplication(Application.create(config.APP)); 9 | 10 | setup(QUnit.assert); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchlloyd/ember-screen/1a77350f6ca6a70fdd4eb0a7dc2191c46616fc7c/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/services/force-media-features-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import EmberScreen, { breakpoint } from 'ember-screen'; 3 | 4 | module('Unit | Service | force-media-features', function () { 5 | test('it lets users force specific media features for testing', function (assert) { 6 | class ScreenService extends EmberScreen { 7 | @breakpoint('tv and (min-width: 3840px)') is4KTV; 8 | } 9 | 10 | let screen = new ScreenService(); 11 | 12 | assert.notOk( 13 | screen.get('is4KTV'), 14 | 'At first screen does not believe it is a 4K TV' 15 | ); 16 | 17 | screen.stubMediaFeatures({ 18 | type: 'tv', 19 | width: '3840px', 20 | }); 21 | 22 | assert.ok( 23 | screen.get('is4KTV'), 24 | 'After stubbing, screen reports it is a 4K TV' 25 | ); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/unit/services/screen-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ember/no-observers */ 2 | import { module, test } from 'qunit'; 3 | import { setupTest } from 'ember-qunit'; 4 | 5 | module('Unit | Service | screen', function (hooks) { 6 | setupTest(hooks); 7 | 8 | hooks.beforeEach(function () { 9 | this.screen = this.owner.lookup('service:screen'); 10 | this.win = this.screen.win; 11 | this.observerCount = 0; 12 | }); 13 | 14 | test('it updates width and height when screen dimensions change', async function (assert) { 15 | const observerCalled = new Promise((resolve) => { 16 | this.screen.addObserver('width', 'height', () => { 17 | this.observerCount += 1; 18 | resolve(); 19 | }); 20 | }); 21 | 22 | this.win.stubMediaFeatures({ width: 100, height: 200 }); 23 | 24 | assert.equal(this.screen.get('width'), 100, 'size was updated'); 25 | assert.equal(this.screen.get('height'), 200, 'height was updated'); 26 | 27 | await observerCalled; 28 | 29 | assert.equal(this.observerCount, 1, 'observer was called once'); 30 | }); 31 | 32 | test('it updates media query properties when screen dimensions change', async function (assert) { 33 | // Get the computed property so that is has been initialized. 34 | this.screen.get('isSmallAndUp'); 35 | 36 | const resolvers = []; 37 | const observer1Called = new Promise((r) => resolvers.push(r)); 38 | const observer2Called = new Promise((r) => resolvers.push(r)); 39 | this.screen.addObserver('isSmallAndUp', () => { 40 | this.observerCount += 1; 41 | resolvers.shift()(); 42 | }); 43 | 44 | this.win.matchesMediaQuery = function () { 45 | return false; 46 | }; 47 | this.win.stubMediaFeatures({ width: 100, height: 200 }); 48 | 49 | await observer1Called; 50 | assert.equal(this.observerCount, 1, 'observer was called once'); 51 | assert.false( 52 | this.screen.get('isSmallAndUp'), 53 | 'media query returns initial stubbed value' 54 | ); 55 | 56 | this.win.matchesMediaQuery = function () { 57 | return true; 58 | }; 59 | this.win.stubMediaFeatures({ width: 200, height: 200 }); 60 | 61 | await observer2Called; 62 | assert.equal(this.observerCount, 2, 'observer was called again'); 63 | assert.true( 64 | this.screen.get('isSmallAndUp'), 65 | 'media query returns new stubbed value' 66 | ); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchlloyd/ember-screen/1a77350f6ca6a70fdd4eb0a7dc2191c46616fc7c/vendor/.gitkeep -------------------------------------------------------------------------------- /vendor/shims/css-mediaquery.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014, Yahoo! Inc. All rights reserved. 3 | Copyrights licensed under the New BSD License. 4 | See the accompanying LICENSE file for terms. 5 | 6 | This file was copied from https://github.com/ericf/css-mediaquery/blob/master/index.js 7 | so that is could be easily used with Ember CLI. 8 | */ 9 | 10 | 11 | (function() { 12 | function vendorModule() { 13 | 'use strict'; 14 | 15 | // Library Start 16 | var RE_MEDIA_QUERY = /^(?:(only|not)?\s*([_a-z][_a-z0-9-]*)|(\([^\)]+\)))(?:\s*and\s*(.*))?$/i, 17 | RE_MQ_EXPRESSION = /^\(\s*([_a-z-][_a-z0-9-]*)\s*(?:\:\s*([^\)]+))?\s*\)$/, 18 | RE_MQ_FEATURE = /^(?:(min|max)-)?(.+)/, 19 | RE_LENGTH_UNIT = /(em|rem|px|cm|mm|in|pt|pc)?\s*$/, 20 | RE_RESOLUTION_UNIT = /(dpi|dpcm|dppx)?\s*$/; 21 | 22 | function matchQuery(mediaQuery, values) { 23 | return parseQuery(mediaQuery).some(function (query) { 24 | var inverse = query.inverse; 25 | 26 | // Either the parsed or specified `type` is "all", or the types must be 27 | // equal for a match. 28 | var typeMatch = query.type === 'all' || values.type === query.type; 29 | 30 | // Quit early when `type` doesn't match, but take "not" into account. 31 | if ((typeMatch && inverse) || !(typeMatch || inverse)) { 32 | return false; 33 | } 34 | 35 | var expressionsMatch = query.expressions.every(function (expression) { 36 | var feature = expression.feature, 37 | modifier = expression.modifier, 38 | expValue = expression.value, 39 | value = values[feature]; 40 | 41 | // Missing or falsy values don't match. 42 | if (!value) { return false; } 43 | 44 | switch (feature) { 45 | case 'orientation': 46 | case 'scan': 47 | return value.toLowerCase() === expValue.toLowerCase(); 48 | 49 | case 'width': 50 | case 'height': 51 | case 'device-width': 52 | case 'device-height': 53 | expValue = toPx(expValue); 54 | value = toPx(value); 55 | break; 56 | 57 | case 'resolution': 58 | expValue = toDpi(expValue); 59 | value = toDpi(value); 60 | break; 61 | 62 | case 'aspect-ratio': 63 | case 'device-aspect-ratio': 64 | case /* Deprecated */ 'device-pixel-ratio': 65 | expValue = toDecimal(expValue); 66 | value = toDecimal(value); 67 | break; 68 | 69 | case 'grid': 70 | case 'color': 71 | case 'color-index': 72 | case 'monochrome': 73 | expValue = parseInt(expValue, 10) || 1; 74 | value = parseInt(value, 10) || 0; 75 | break; 76 | } 77 | 78 | switch (modifier) { 79 | case 'min': return value >= expValue; 80 | case 'max': return value <= expValue; 81 | default : return value === expValue; 82 | } 83 | }); 84 | 85 | return (expressionsMatch && !inverse) || (!expressionsMatch && inverse); 86 | }); 87 | } 88 | 89 | function parseQuery(mediaQuery) { 90 | return mediaQuery.split(',').map(function (query) { 91 | query = query.trim(); 92 | 93 | var captures = query.match(RE_MEDIA_QUERY); 94 | 95 | // Media Query must be valid. 96 | if (!captures) { 97 | throw new SyntaxError('Invalid CSS media query: "' + query + '"'); 98 | } 99 | 100 | var modifier = captures[1], 101 | type = captures[2], 102 | expressions = ((captures[3] || '') + (captures[4] || '')).trim(), 103 | parsed = {}; 104 | 105 | parsed.inverse = !!modifier && modifier.toLowerCase() === 'not'; 106 | parsed.type = type ? type.toLowerCase() : 'all'; 107 | 108 | // Check for media query expressions. 109 | if (!expressions) { 110 | parsed.expressions = []; 111 | return parsed; 112 | } 113 | 114 | // Split expressions into a list. 115 | expressions = expressions.match(/\([^\)]+\)/g); 116 | 117 | // Media Query must be valid. 118 | if (!expressions) { 119 | throw new SyntaxError('Invalid CSS media query: "' + query + '"'); 120 | } 121 | 122 | parsed.expressions = expressions.map(function (expression) { 123 | var captures = expression.match(RE_MQ_EXPRESSION); 124 | 125 | // Media Query must be valid. 126 | if (!captures) { 127 | throw new SyntaxError('Invalid CSS media query: "' + query + '"'); 128 | } 129 | 130 | var feature = captures[1].toLowerCase().match(RE_MQ_FEATURE); 131 | 132 | return { 133 | modifier: feature[1], 134 | feature : feature[2], 135 | value : captures[2] 136 | }; 137 | }); 138 | 139 | return parsed; 140 | }); 141 | } 142 | 143 | // -- Utilities ---------------------------------------------------------------- 144 | 145 | function toDecimal(ratio) { 146 | var decimal = Number(ratio), 147 | numbers; 148 | 149 | if (!decimal) { 150 | numbers = ratio.match(/^(\d+)\s*\/\s*(\d+)$/); 151 | decimal = numbers[1] / numbers[2]; 152 | } 153 | 154 | return decimal; 155 | } 156 | 157 | function toDpi(resolution) { 158 | var value = parseFloat(resolution), 159 | units = String(resolution).match(RE_RESOLUTION_UNIT)[1]; 160 | 161 | switch (units) { 162 | case 'dpcm': return value / 2.54; 163 | case 'dppx': return value * 96; 164 | default : return value; 165 | } 166 | } 167 | 168 | function toPx(length) { 169 | var value = parseFloat(length), 170 | units = String(length).match(RE_LENGTH_UNIT)[1]; 171 | 172 | switch (units) { 173 | case 'em' : return value * 16; 174 | case 'rem': return value * 16; 175 | case 'cm' : return value * 96 / 2.54; 176 | case 'mm' : return value * 96 / 2.54 / 10; 177 | case 'in' : return value * 96; 178 | case 'pt' : return value * 72; 179 | case 'pc' : return value * 72 / 12; 180 | default : return value; 181 | } 182 | } 183 | // Library End 184 | 185 | return { matchQuery: matchQuery, parseQuery: parseQuery }; 186 | } 187 | 188 | define('css-mediaquery', [], vendorModule); 189 | })(); 190 | --------------------------------------------------------------------------------