├── .babelrc ├── .codecov.yml ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── package-lock.json ├── package.json ├── src ├── adapter │ ├── index.ts │ ├── util │ │ └── inspect.ts │ └── viewport.ts ├── config │ ├── default.json │ └── schema.json ├── index.ts ├── static │ └── debug.html └── tslint.json ├── tests ├── config │ ├── browsers │ │ ├── integration.json │ │ └── unit.json │ └── index.ts ├── fixtures │ └── default.html ├── helpers │ └── index.ts ├── karma.conf.ts ├── karma.integration.conf.ts ├── mocks │ ├── adapter │ │ └── viewport.ts │ └── vendor │ │ └── document.ts ├── suites │ ├── integration │ │ └── viewport.spec.ts │ └── unit │ │ ├── util │ │ └── inspect.spec.ts │ │ └── viewport.spec.ts ├── tsconfig.json └── tslint.json ├── tsconfig.json ├── tslint.json ├── typings ├── jsonschema.d.ts ├── karma.d.ts └── project-name-generator.d.ts └── webpack.config.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "modules": false, 5 | "targets": " > 1%, last 2 versions" 6 | }] 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017-2020 Martin Donath 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to 5 | # deal in the Software without restriction, including without limitation the 6 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | # sell copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | # IN THE SOFTWARE. 20 | 21 | coverage: 22 | 23 | # Coverage status 24 | status: 25 | 26 | # Overall project status 27 | project: 28 | default: 29 | enabled: yes 30 | target: auto 31 | threshold: 1% 32 | if_no_uploads: error 33 | if_not_found: success 34 | if_ci_failed: error 35 | 36 | # Pull request coverage diff 37 | patch: 38 | default: 39 | enabled: yes 40 | target: 95% 41 | threshold: 1% 42 | if_no_uploads: error 43 | if_not_found: success 44 | if_ci_failed: error 45 | 46 | # Unexpected changes 47 | changes: 48 | default: 49 | enabled: yes 50 | if_no_uploads: error 51 | if_not_found: success 52 | if_ci_failed: error 53 | 54 | # Comments inside pull requests 55 | comment: 56 | layout: header, tree, changes, files 57 | behavior: default 58 | paths: 59 | - src 60 | - tests 61 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017-2020 Martin Donath 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to 5 | # deal in the Software without restriction, including without limitation the 6 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | # sell copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | # IN THE SOFTWARE. 20 | 21 | # Top-level config 22 | root = true 23 | 24 | # All files 25 | [*] 26 | charset = utf-8 27 | indent_style = space 28 | indent_size = 2 29 | end_of_line = lf 30 | insert_final_newline = true 31 | trim_trailing_whitespace = true 32 | 33 | # Makefiles 34 | [Makefile] 35 | indent_style = tab 36 | indent_size = 8 37 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017-2020 Martin Donath 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to 5 | # deal in the Software without restriction, including without limitation the 6 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | # sell copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | # IN THE SOFTWARE. 20 | 21 | github: squidfunk 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Description of the bug] 4 | 5 | ### Expected behavior 6 | 7 | [What you expected to happen] 8 | 9 | ### Actual behavior 10 | 11 | [What is actually happening] 12 | 13 | ### Steps to reproduce the bug 14 | 15 | 1. [First step] 16 | 2. [Second step] 17 | 3. [and so on...] 18 | 19 | ### Package versions 20 | 21 | * karma-viewport: `...` 22 | * karma: `...` 23 | 24 | ### System information 25 | 26 | * OS: [The operating system you're running] 27 | * Browser: [The browser used, if relevant] 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017-2020 Martin Donath 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to 5 | # deal in the Software without restriction, including without limitation the 6 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | # sell copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | # IN THE SOFTWARE. 20 | 21 | name: ci 22 | on: 23 | - push 24 | - pull_request 25 | 26 | # Jobs to run 27 | jobs: 28 | 29 | # Build theme 30 | build: 31 | runs-on: ubuntu-latest 32 | strategy: 33 | matrix: 34 | node-version: [12.x, 14.x, 16.x] 35 | 36 | steps: 37 | 38 | # Limit clone depth to speed up build 39 | - uses: actions/checkout@v1 40 | with: 41 | fetch-depth: 5 42 | 43 | # Install Node runtime and dependencies 44 | - uses: actions/setup-node@v1 45 | with: 46 | node-version: ${{ matrix.node-version }} 47 | 48 | - uses: actions/cache@v1 49 | id: cache-node 50 | with: 51 | path: node_modules 52 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 53 | 54 | - if: steps.cache-node.outputs.cache-hit != 'true' 55 | run: npm install 56 | 57 | # Run linter and build distribution files 58 | - run: npm run lint 59 | - run: npm run build 60 | - run: npm run test 61 | - run: npm run test:integration 62 | 63 | # Upload coverage report to codecov.io 64 | # - run: | 65 | # npm install codecov 66 | # npx codecov 67 | # env: 68 | # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 69 | 70 | # Publish Python package and Docker image 71 | publish: 72 | if: | 73 | github.repository == 'squidfunk/karma-viewport' && 74 | startsWith(github.ref, 'refs/tags') 75 | needs: build 76 | runs-on: ubuntu-latest 77 | steps: 78 | 79 | # Limit clone depth to speed up build 80 | - uses: actions/checkout@v1 81 | with: 82 | fetch-depth: 5 83 | 84 | # Install Node runtime and dependencies 85 | - uses: actions/setup-node@v1 86 | with: 87 | node-version: 10.x 88 | registry-url: https://registry.npmjs.org 89 | 90 | - uses: actions/cache@v1 91 | id: cache-node 92 | with: 93 | path: node_modules 94 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 95 | 96 | - if: steps.cache-node.outputs.cache-hit != 'true' 97 | run: npm install 98 | 99 | # Run build and publish to NPM 100 | - run: npm run build 101 | - env: 102 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 103 | run: npm publish 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017-2020 Martin Donath 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to 5 | # deal in the Software without restriction, including without limitation the 6 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | # sell copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | # IN THE SOFTWARE. 20 | 21 | # macOS internals 22 | .DS_Store 23 | 24 | # NPM-related 25 | /node_modules 26 | /npm-debug.log* 27 | 28 | # Distribution files 29 | /dist 30 | /package 31 | 32 | # Coverage reports 33 | /coverage 34 | 35 | # Generated typings 36 | src/**/*.d.ts 37 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | karma-viewport-1.0.9 (2022-04-04) 2 | 3 | * Upgraded dependencies and fixed security vulnerabilities 4 | 5 | karma-viewport-1.0.8 (2021-02-04) 6 | 7 | * Fixed source map warnings 8 | 9 | karma-viewport-1.0.7 (2020-08-30) 10 | 11 | * Added source maps to served files to fix 404 warnings 12 | 13 | karma-viewport-1.0.6 (2020-03-24) 14 | 15 | * Upgraded dependencies and fixed security vulnerabilities 16 | 17 | karma-viewport-1.0.5 (2020-01-29) 18 | 19 | * Fixed #113: Invalid file type warning 20 | 21 | karma-viewport-1.0.4 (2019-01-10) 22 | 23 | * Fixed TypeScript typings 24 | 25 | karma-viewport-1.0.3 (2018-12-26) 26 | 27 | * Updated all dependencies to fix security vulnerabilities 28 | 29 | karma-viewport-1.0.2 (2018-03-28) 30 | 31 | * Removed unnecessarily exported typing 32 | 33 | karma-viewport-1.0.1 (2018-03-27) 34 | 35 | * Improved typings 36 | 37 | karma-viewport-1.0.0 (2018-03-26) 38 | 39 | * Refactored complete code base into TypeScript 40 | * Added Promise support for callback functions 41 | * Added typings to distribution files 42 | * Improved forced layout recalculation 43 | * Improved unit and integration tests 44 | 45 | karma-viewport-0.4.2 (2017-09-05) 46 | 47 | * Fixed debug context middleware not being registered 48 | 49 | karma-viewport-0.4.1 (2017-08-09) 50 | 51 | * Added support to load documents in iframe 52 | 53 | karma-viewport-0.4.0 (2017-08-08) 54 | 55 | * Renamed configuration option selector into context 56 | 57 | karma-viewport-0.3.0 (2017-08-08) 58 | 59 | * Added support to merge configuration with defaults 60 | * Added support for customizable viewport element 61 | 62 | karma-viewport-0.2.1 (2017-08-01) 63 | 64 | * Fixed undefined variable in middleware 65 | 66 | karma-viewport-0.2.0 (2017-08-01) 67 | 68 | * Added iframe usage as a prerequisite 69 | * Added iframe for debug context 70 | * Replaced inspect with lightweight version 71 | 72 | karma-viewport-0.1.2 (2017-07-08) 73 | 74 | * Fixed Karma plugin integration 75 | * Added smoke test 76 | 77 | karma-viewport-0.1.1 (2017-07-07) 78 | 79 | * Fixed broken Travis build 80 | 81 | karma-viewport-0.1.0 (2017-07-07) 82 | 83 | * Initial release 84 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, 8 | body size, disability, ethnicity, gender identity and expression, level of 9 | experience, nationality, personal appearance, race, religion, or sexual 10 | identity and orientation. 11 | 12 | ## Our standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an 52 | appointed representative at an online or offline event. Representation of a 53 | project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at 59 | https://gitter.im/squidfunk/karma-viewport. The project team will review and 60 | investigate all complaints, and will respond in a way that it deems appropriate 61 | to the circumstances. The project team is obligated to maintain confidentiality 62 | with regard to the reporter of an incident. Further details of specific 63 | enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][1], version 1.4, 72 | available at [http://contributor-covenant.org/version/1/4][2] 73 | 74 | [1]: http://contributor-covenant.org 75 | [2]: http://contributor-covenant.org/version/1/4/ 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Interested in contributing to this project? Want to report a bug? Before you do, 4 | please read the following guidelines. 5 | 6 | ## Submission context 7 | 8 | ### Got a question or problem? 9 | 10 | For quick questions there's no need to open an issue as you can reach us on 11 | [gitter.im][1]. 12 | 13 | [1]: https://gitter.im/squidfunk/karma-viewport 14 | 15 | ### Found a bug? 16 | 17 | If you found a bug, you can help us by submitting an issue to the 18 | [issue tracker][2] in our GitHub repository. Even better, you can submit 19 | a pull request with a fix. However, before doing so, please read the 20 | [submission guidelines][3]. 21 | 22 | [2]: https://github.com/squidfunk/karma-viewport/issues 23 | [3]: #submission-guidelines 24 | 25 | ### Missing a feature? 26 | 27 | You can request a new feature by submitting an issue to our GitHub Repository. 28 | If you would like to implement a new feature, please submit an issue with a 29 | proposal for your work first, to be sure that it is of use for everyone. 30 | Please consider what kind of change it is: 31 | 32 | * For a **major feature**, first open an issue and outline your proposal so 33 | that it can be discussed. This will also allow us to better coordinate our 34 | efforts, prevent duplication of work and help you to craft the change so 35 | that it is successfully accepted into the project. 36 | 37 | * **Small features and bugs** can be crafted and directly submitted as a pull 38 | request (PR). However, there is no guarantee that your feature will make it 39 | into the master, as it's always a matter of opinion whether if benefits the 40 | overall functionality of this project. 41 | 42 | ## Submission guidelines 43 | 44 | ### Submitting an issue 45 | 46 | Before you submit an issue, please search the issue tracker. Maybe the problem 47 | you are encountering is already known and the discussion might inform you of 48 | workarounds readily available. 49 | 50 | We want to address all the issues as soon as possible, but before fixing a bug 51 | we first need to reproduce and confirm it. In order to reproduce bugs we will 52 | systematically ask you to provide a minimal reproduction scenario using the 53 | custom issue template. Please stick to the issue template. 54 | 55 | Unfortunately we are not able to investigate / fix bugs without a minimal 56 | reproduction scenario, so if we don't hear back from you we may close the issue. 57 | 58 | ### Submitting a pull request 59 | 60 | Search GitHub for an open or closed PR that relates to your submission. You 61 | don't want to duplicate effort. If you do not find a related issue or PR, 62 | go ahead. 63 | 64 | 1. Fork the project, make your changes in a separate git branch and add 65 | descriptive messages to your commits. 66 | 67 | 2. Push your branch to GitHub and send a PR to `karma-viewport:master`. If we 68 | suggest changes, rebase your branch and push the changes to your GitHub 69 | repository, which will automatically update your PR. 70 | 71 | After your PR is merged, you can safely delete your branch and pull the changes 72 | from the main (upstream) repository. 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2020 Martin Donath 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017-2020 Martin Donath 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to 5 | # deal in the Software without restriction, including without limitation the 6 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | # sell copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | # IN THE SOFTWARE. 20 | 21 | all: clean lint | build test/integration test 22 | 23 | # ----------------------------------------------------------------------------- 24 | # Prerequisites 25 | # ----------------------------------------------------------------------------- 26 | 27 | # Install dependencies 28 | node_modules: 29 | npm install 30 | 31 | # ----------------------------------------------------------------------------- 32 | # Targets 33 | # ----------------------------------------------------------------------------- 34 | 35 | # Build theme for distribution with Webpack 36 | dist/adapter/index.js: $(shell find src/adapter) \ 37 | .babelrc webpack.config.ts dist/index.js 38 | $(shell npm bin)/webpack --mode production 39 | 40 | # Create directories 41 | dist/config dist/static: 42 | mkdir -p $@ 43 | 44 | # Copy configuration files 45 | dist/config/%.json: src/config/%.json dist/config 46 | cp $< $@ 47 | 48 | # Copy configuration files 49 | dist/static/%.html: src/static/%.html dist/static 50 | cp $< $@ 51 | 52 | # Build distribution files 53 | dist/index.js: src/index.ts 54 | $(shell npm bin)/tsc -p tsconfig.json 55 | rm -rf dist/adapter 56 | 57 | # ----------------------------------------------------------------------------- 58 | # Rules 59 | # ----------------------------------------------------------------------------- 60 | 61 | # Build distribution files 62 | build: \ 63 | node_modules \ 64 | dist/adapter/index.js \ 65 | dist/config/default.json \ 66 | dist/config/schema.json \ 67 | dist/static/debug.html \ 68 | dist/index.js 69 | 70 | # Clean distribution files 71 | clean: 72 | rm -rf coverage dist 73 | 74 | # Lint source files 75 | lint: node_modules 76 | $(shell npm bin)/tslint -p tsconfig.json "src/**/*.ts" 77 | $(shell npm bin)/tslint -p tests/tsconfig.json "tests/**/*.ts" 78 | 79 | # Execute integration tests 80 | test-integration: node_modules build 81 | TS_NODE_PROJECT=tests/tsconfig.json TS_NODE_FILES=true \ 82 | $(shell npm bin)/karma start tests/karma.integration.conf.ts \ 83 | --single-run 84 | 85 | # Execute unit tests 86 | test: node_modules 87 | TS_NODE_PROJECT=tests/tsconfig.json TS_NODE_FILES=true \ 88 | $(shell npm bin)/karma start tests/karma.conf.ts --single-run 89 | 90 | # Execute unit tests in watch mode 91 | watch: node_modules 92 | TS_NODE_PROJECT=tests/tsconfig.json TS_NODE_FILES=true \ 93 | $(shell npm bin)/karma start tests/karma.conf.ts 94 | 95 | # ----------------------------------------------------------------------------- 96 | 97 | # Special targets 98 | .PHONY: .FORCE build clean lint test/integration test watch 99 | .FORCE: 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Github Action][action-image]][action-link] 2 | [![Codecov][codecov-image]][codecov-link] 3 | ![Downloads][npm-downloads] 4 | [![npm][npm-image]][npm-link] 5 | 6 | [action-image]: https://github.com/squidfunk/karma-viewport/workflows/ci/badge.svg?branch=master 7 | [action-link]: https://github.com/squidfunk/karma-viewport/actions 8 | [codecov-image]: https://img.shields.io/codecov/c/github/squidfunk/karma-viewport/master.svg 9 | [codecov-link]: https://codecov.io/gh/squidfunk/karma-viewport 10 | [npm-image]: https://img.shields.io/npm/v/karma-viewport.svg 11 | [npm-link]: https://npmjs.com/package/karma-viewport 12 | [npm-downloads]: https://img.shields.io/npm/dm/karma-viewport 13 | 14 | # karma-viewport 15 | 16 | Karma viewport resizer for testing responsive features and layout 17 | 18 | ## Installation 19 | 20 | ``` sh 21 | npm install karma-viewport 22 | ``` 23 | 24 | ## Usage 25 | 26 | Add `viewport` to the list of frameworks inside your Karma configuration: 27 | 28 | ``` js 29 | // karma.conf.js 30 | module.exports = function(config) { 31 | config.set({ 32 | frameworks: ["viewport"] 33 | }) 34 | } 35 | ``` 36 | 37 | This will expose the global variable `viewport` to your tests, which allows 38 | setting the dimensions of the viewport, e.g.: 39 | 40 | ``` js 41 | // Set to 320px x 100% 42 | viewport.set(320) 43 | 44 | // Set to 320px x 480px 45 | viewport.set(320, 480) 46 | 47 | // Reset to 100% x 100% 48 | viewport.reset() 49 | ``` 50 | 51 | _Note that you (probably) cannot use `viewport` in the top-level scope of your 52 | tests, as Karma might not have initialized all plugins until all files were 53 | read, so makes sure to call the respective functions from the setup hooks of 54 | your test framework or from within your tests._ 55 | 56 | ### Browser support 57 | 58 | Chrome, Firefox, Safari, Edge 13-15, IE 9-11 and possibly some more. 59 | 60 | ### Configuration 61 | 62 | #### `config.viewport.context` 63 | 64 | By default, `viewport` will target the default `iframe#context` of Karma, 65 | which is enabled through `client.useIframe` (see the [configuration guide][1]). 66 | This will also wrap the `debug` context to run inside the `iframe#context`. 67 | 68 | To run tests within a custom, separate context, e.g. `iframe#viewport`: 69 | 70 | ``` js 71 | // karma.conf.js 72 | module.exports = function(config) { 73 | config.set({ 74 | frameworks: ["viewport"] 75 | 76 | // Viewport configuration 77 | viewport: { 78 | context: "#viewport" 79 | } 80 | }) 81 | } 82 | ``` 83 | 84 | Note that the `iframe#viewport` element must be present in the `context.html` 85 | and `debug.html` files that are served by Karma. You can override the files, or 86 | add an `iframe` element dynamically before running the tests. Using a separate, 87 | custom context makes it possible to load entire webpages for testing: 88 | 89 | ``` js 90 | await viewport.load("/path/to/fixture.html") 91 | ``` 92 | 93 | [1]: http://karma-runner.github.io/1.0/config/configuration-file.html 94 | 95 | #### `config.viewport.breakpoints` 96 | 97 | For easier, and less repetitive testing, named breakpoints can be easily set: 98 | 99 | ``` js 100 | // karma.conf.js 101 | module.exports = function(config) { 102 | config.set({ 103 | frameworks: ["viewport"] 104 | 105 | // Viewport configuration 106 | viewport: { 107 | breakpoints: [ 108 | { 109 | name: "mobile", 110 | size: { 111 | width: 320, 112 | height: 480 113 | } 114 | }, 115 | { 116 | name: "tablet", 117 | size: { 118 | width: 768, 119 | height: 1024 120 | } 121 | }, 122 | { 123 | name: "screen", 124 | size: { 125 | width: 1440, 126 | height: 900 127 | } 128 | } 129 | ] 130 | } 131 | }) 132 | } 133 | ``` 134 | 135 | The viewport dimensions can then be set using the names of the breakpoints: 136 | 137 | ``` js 138 | // Set to 320px x 480px 139 | viewport.set("mobile") 140 | 141 | // Set to 1440px x 900px 142 | viewport.set("screen") 143 | 144 | // Reset to 100% x 100% 145 | viewport.reset() 146 | ``` 147 | 148 | Furthermore, breakpoints can be iterated: 149 | 150 | ``` js 151 | // Run tests for mobile, tablet and screen 152 | viewport.each(name => { 153 | // ... 154 | }) 155 | 156 | // Run tests for tablet and screen 157 | viewport.from("tablet", name => { 158 | // ... 159 | }) 160 | 161 | // Run tests for mobile and tablet 162 | viewport.to("tablet", name => { 163 | // ... 164 | }) 165 | 166 | // Run tests for tablet and screen 167 | viewport.between("tablet", "screen", name => { 168 | // ... 169 | }) 170 | ``` 171 | 172 | After breakpoint iteration, `viewport.reset()` is called internally. If the 173 | callback provided to the breakpoint returns a `Promise`, the return value of 174 | the function will also be a `Promise`. This enables asynchronous tests: 175 | 176 | ``` js 177 | viewport.each(async name => { 178 | // await ... 179 | }) 180 | ``` 181 | 182 | ### TypeScript 183 | 184 | `karma-viewport` is written in TypeScript and comes with its own typings. Don't 185 | include the package using an `import` statement, but instead include its types 186 | via `tsconfig.json` or a reference within `karma.conf.ts` or tests: 187 | 188 | ``` ts 189 | /// 190 | ``` 191 | 192 | ## License 193 | 194 | **MIT License** 195 | 196 | Copyright (c) 2017-2020 Martin Donath 197 | 198 | Permission is hereby granted, free of charge, to any person obtaining a copy 199 | of this software and associated documentation files (the "Software"), to 200 | deal in the Software without restriction, including without limitation the 201 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 202 | sell copies of the Software, and to permit persons to whom the Software is 203 | furnished to do so, subject to the following conditions: 204 | 205 | The above copyright notice and this permission notice shall be included in 206 | all copies or substantial portions of the Software. 207 | 208 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 209 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 210 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 211 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 212 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 213 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 214 | IN THE SOFTWARE. 215 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "karma-viewport", 3 | "version": "1.0.9", 4 | "description": "A Karma plugin for testing responsive features and layout", 5 | "keywords": [ 6 | "breakpoint", 7 | "iframe", 8 | "karma", 9 | "karma-framework", 10 | "karma-plugin", 11 | "responsive", 12 | "testing", 13 | "viewport" 14 | ], 15 | "homepage": "https://github.com/squidfunk/karma-viewport", 16 | "bugs": { 17 | "url": "https://github.com/squidfunk/karma-viewport/issues", 18 | "email": "martin.donath@squidfunk.com" 19 | }, 20 | "license": "MIT", 21 | "author": { 22 | "name": "Martin Donath", 23 | "email": "martin.donath@squidfunk.com" 24 | }, 25 | "contributors": [], 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/squidfunk/karma-viewport.git" 29 | }, 30 | "files": [ 31 | "dist" 32 | ], 33 | "main": "dist/index.js", 34 | "types": "dist/index.d.ts", 35 | "scripts": { 36 | "build": "make build", 37 | "clean": "make clean", 38 | "lint": "make lint", 39 | "test": "make test", 40 | "test:integration": "make test-integration", 41 | "watch": "make watch" 42 | }, 43 | "dependencies": { 44 | "@types/karma": "^6.3.3", 45 | "jsonschema": "^1.4.0" 46 | }, 47 | "devDependencies": { 48 | "@babel/core": "^7.17.8", 49 | "@babel/preset-env": "^7.16.11", 50 | "@types/chance": "^1.1.3", 51 | "@types/jasmine": "^3.5.14", 52 | "@types/jsonschema": "^1.1.1", 53 | "@types/karma-fixture": "^0.2.5", 54 | "@types/node": "^17.0.23", 55 | "@types/webpack": "^5.28.0", 56 | "array-findindex-polyfill": "^0.1.0", 57 | "babel-loader": "^8.2.4", 58 | "chance": "^1.1.8", 59 | "codecov": "^3.8.2", 60 | "es6-promise": "^4.2.8", 61 | "istanbul-instrumenter-loader": "^3.0.1", 62 | "jasmine-core": "^3.6.0", 63 | "karma": "^6.3.17", 64 | "karma-chrome-launcher": "^3.1.1", 65 | "karma-clear-screen-reporter": "^1.0.0", 66 | "karma-coverage-istanbul-reporter": "^3.0.3", 67 | "karma-firefox-launcher": "^2.1.2", 68 | "karma-fixture": "^0.2.6", 69 | "karma-html2js-preprocessor": "^1.1.0", 70 | "karma-jasmine": "^4.0.2", 71 | "karma-sourcemap-loader": "^0.3.8", 72 | "karma-spec-reporter": "^0.0.33", 73 | "karma-summary-reporter": "^3.1.1", 74 | "karma-webpack": "^4.0.2", 75 | "project-name-generator": "^2.1.9", 76 | "ts-loader": "^8.0.3", 77 | "ts-node": "^10.7.0", 78 | "tsconfig-paths-webpack-plugin": "^3.5.2", 79 | "tslint": "^6.1.3", 80 | "typescript": "^4.6.3", 81 | "webpack": "^4.44.1", 82 | "webpack-cli": "^4.0.0" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/adapter/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import { 24 | Viewport, 25 | ViewportConfiguration 26 | } from "./viewport" 27 | 28 | /* ---------------------------------------------------------------------------- 29 | * Types 30 | * ------------------------------------------------------------------------- */ 31 | 32 | /** 33 | * Extend window element with custom options and viewport instance 34 | */ 35 | declare global { 36 | interface Window { 37 | __viewport__: ViewportConfiguration 38 | viewport: Viewport 39 | } 40 | } 41 | 42 | /* ---------------------------------------------------------------------------- 43 | * Initialization 44 | * ------------------------------------------------------------------------- */ 45 | 46 | ((window, config) => { 47 | window.viewport = new Viewport(config, window) 48 | })(window, window.__viewport__) 49 | -------------------------------------------------------------------------------- /src/adapter/util/inspect.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | /* ---------------------------------------------------------------------------- 24 | * Types 25 | * ------------------------------------------------------------------------- */ 26 | 27 | /** 28 | * Arbitary function for value inspection 29 | * 30 | * @param args - Arguments 31 | * 32 | * @return Return value 33 | */ 34 | export type InspectFunction = (...args: any[]) => any 35 | 36 | /** 37 | * Input type for value inspection 38 | */ 39 | export type InspectValue = 40 | undefined | null | number | string | object | InspectFunction 41 | 42 | /* ---------------------------------------------------------------------------- 43 | * Functions 44 | * ------------------------------------------------------------------------- */ 45 | 46 | /** 47 | * Super-lightweight value inspection 48 | * 49 | * util.inspect for the poor. However, it works reasonably well and does not 50 | * blow up the size of the bundle by 15kb. 51 | * 52 | * @param value - Value to inspect 53 | * 54 | * @return Stringified value 55 | */ 56 | export function inspect(value: InspectValue): string { 57 | switch (typeof value) { 58 | case "object": 59 | return JSON.stringify(value, undefined, 2).replace(/\s+/g, " ") 60 | case "string": 61 | return `'${value}'` 62 | } 63 | return ("" + value).replace(/\s+/g, " ") 64 | } 65 | -------------------------------------------------------------------------------- /src/adapter/viewport.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | /** @internal polyfill Array.findIndex */ 24 | import "array-findindex-polyfill" 25 | 26 | import { inspect } from "./util/inspect" 27 | 28 | /* ---------------------------------------------------------------------------- 29 | * Types 30 | * ------------------------------------------------------------------------- */ 31 | 32 | /** 33 | * Viewport breakpoint 34 | */ 35 | export interface ViewportBreakpoint { 36 | name: string /* Breakpoint name */ 37 | size: { 38 | width: number /* Viewport width */ 39 | height: number /* Viewport height */ 40 | } 41 | } 42 | 43 | /** 44 | * Viewport configuration 45 | */ 46 | export interface ViewportConfiguration { 47 | context: string /* Context element selector */ 48 | breakpoints: ViewportBreakpoint[] /* Breakpoints */ 49 | } 50 | 51 | /** 52 | * Viewport callback 53 | */ 54 | export type ViewportCallback = (breakpoint: string) => T 55 | 56 | /** 57 | * Extend window element with missing types 58 | * 59 | * @internal 60 | */ 61 | declare global { 62 | interface Window { 63 | HTMLIFrameElement: typeof HTMLIFrameElement 64 | } 65 | } 66 | 67 | /* ---------------------------------------------------------------------------- 68 | * Functions 69 | * ------------------------------------------------------------------------- */ 70 | 71 | /** 72 | * Resolve relevant breakpoint range 73 | * 74 | * @param breakpoints - Breakpoints 75 | * @param first - First breakpoint name 76 | * @param last - Last breakpoint name 77 | * 78 | * @return Selected breakpoints 79 | */ 80 | export function range( 81 | breakpoints: ViewportBreakpoint[], first: string, last: string = first 82 | ) { 83 | const [from, to] = [first, last].map(name => { 84 | const index = breakpoints.findIndex( 85 | breakpoint => breakpoint.name === name) 86 | if (index === -1) 87 | throw new ReferenceError(`Invalid breakpoint: ${inspect(name)}`) 88 | return index 89 | }) 90 | 91 | /* Return relevant breakpoints */ 92 | return breakpoints.slice(from, to + 1) 93 | } 94 | 95 | /** 96 | * Type guard for Promise 97 | * 98 | * @param value - Value to be checked 99 | * 100 | * @return Whether the value is a Promise 101 | */ 102 | function isPromise(value: any): value is Promise { 103 | return Promise.resolve(value) === value 104 | } 105 | 106 | /* ---------------------------------------------------------------------------- 107 | * Class 108 | * ------------------------------------------------------------------------- */ 109 | 110 | export class Viewport { 111 | 112 | /** 113 | * Viewport configuration 114 | */ 115 | public config: Readonly 116 | 117 | /** 118 | * Viewport context 119 | */ 120 | public context: HTMLIFrameElement 121 | 122 | /** 123 | * Create viewport resizer 124 | * 125 | * @constructor 126 | * 127 | * @param config - Viewport configuration 128 | * @param parent - Initialization context 129 | */ 130 | public constructor(config: ViewportConfiguration, parent: Window) { 131 | 132 | /* Retrieve context element travelling up */ 133 | let current = parent 134 | let context = parent.document.querySelector(config.context) 135 | while (!context && current !== current.parent) { 136 | current = current.parent 137 | context = current.document.querySelector(config.context) 138 | } 139 | if (!(context instanceof current.HTMLIFrameElement)) 140 | throw new ReferenceError( 141 | `No match for selector: ${inspect(config.context)}`) 142 | 143 | /* Set configuration and context element */ 144 | this.config = config 145 | this.context = context 146 | } 147 | 148 | /** 149 | * Load and embed document into viewport 150 | * 151 | * @param url - URL of document to load 152 | * 153 | * @return Promise resolving with no result 154 | */ 155 | public load(url: string, cb?: () => void) { 156 | return new Promise(resolve => { 157 | const load = () => { 158 | this.context.removeEventListener("load", load) 159 | if (cb) cb() 160 | resolve() 161 | } 162 | this.context.addEventListener("load", load) 163 | this.context.src = url 164 | }) 165 | } 166 | 167 | /** 168 | * Change viewport offset (scroll within iframe) 169 | * 170 | * @param x - Horizontal offset 171 | * @param y - Vertical offset 172 | */ 173 | public offset(x: number, y: number = 0) { 174 | this.context.contentWindow!.scrollTo(x, y) 175 | } 176 | 177 | /** 178 | * Set viewport to width (and height) or breakpoint name 179 | * 180 | * @param widthOrBreakpoint - Width in pixels or breakpoint name 181 | * @param height - Height in pixels 182 | */ 183 | public set(width: number, height?: number): void 184 | public set(breakpoint: string): void 185 | public set(widthOrBreakpoint: number | string, height?: number) { 186 | 187 | /* Set viewport by breakpoint name */ 188 | if (typeof widthOrBreakpoint === "string") { 189 | const [breakpoint] = range(this.config.breakpoints, widthOrBreakpoint) 190 | return this.set(breakpoint.size.width, breakpoint.size.height) 191 | 192 | /* Set viewport width (and height) */ 193 | } else { 194 | const width = widthOrBreakpoint 195 | if (typeof width !== "number" || width <= 0) 196 | throw new TypeError(`Invalid breakpoint width: ${width}`) 197 | if (height && (typeof height !== "number" || height <= 0)) 198 | throw new TypeError(`Invalid breakpoint height: ${height}`) 199 | 200 | /* Set width and height */ 201 | this.context.style.width = `${width}px` 202 | if (height) 203 | this.context.style.height = `${height}px` 204 | } 205 | 206 | /* Force layout, so styles are sure to propagate */ 207 | this.context.contentDocument!.body.getBoundingClientRect() 208 | } 209 | 210 | /** 211 | * Reset viewport 212 | */ 213 | public reset() { 214 | this.context.contentWindow!.scrollTo(0, 0) 215 | this.context.style.width = "" 216 | this.context.style.height = "" 217 | 218 | /* Force layout, so styles are sure to propagate */ 219 | this.context.contentDocument!.body.getBoundingClientRect() 220 | } 221 | 222 | /** 223 | * Execute a callback for all breakpoints between the first and last given 224 | * 225 | * If the callback return value is a Promise, callback invocations will be 226 | * chained to guarantee sequential execution. 227 | * 228 | * @example 229 | * viewport.between("mobile", "tablet", name => { 230 | * ... 231 | * }) 232 | * 233 | * @param first - First breakpoint name 234 | * @param last - Last breakpoint name 235 | * @param cb - Callback to execute after resizing 236 | * 237 | * @return Promise resolving with no result 238 | */ 239 | public between>( 240 | first: string, last: string, cb: ViewportCallback 241 | ): Promise 242 | public between(first: string, last: string, cb: ViewportCallback): void 243 | public between( 244 | first: string, last: string, cb: ViewportCallback 245 | ): void | Promise { 246 | const [initial, ...rest] = range(this.config.breakpoints, first, last) 247 | 248 | /* Apply breakpoint and execute callback */ 249 | const invoke = (breakpoint: ViewportBreakpoint) => { 250 | this.set(breakpoint.size.width, breakpoint.size.height) 251 | return cb(breakpoint.name) 252 | } 253 | 254 | /* Execute the first callback and check if it returns a Promise, as we 255 | need to make sure that everything is executed sequentially */ 256 | const result = invoke(initial) 257 | if (isPromise(result)) 258 | return rest 259 | 260 | /* Resolve breakpoints and execute callback after resizing */ 261 | .reduce((promise: Promise, breakpoint) => { 262 | return promise.then(() => breakpoint).then(invoke) 263 | }, result) 264 | 265 | /* Reset viewport */ 266 | .then(() => this.reset()) 267 | 268 | /* Invoke callback and reset viewport */ 269 | rest.forEach(invoke) 270 | this.reset() 271 | } 272 | 273 | /** 274 | * Execute a callback for all breakpoints 275 | * 276 | * @example 277 | * viewport.each(name => { 278 | * ... 279 | * }) 280 | * 281 | * @param cb - Callback to execute after resizing 282 | * 283 | * @return Promise resolving with no result 284 | */ 285 | public each>(cb: ViewportCallback): Promise 286 | public each(cb: ViewportCallback): void 287 | public each(cb: ViewportCallback): void | Promise { 288 | return this.between(this.config.breakpoints[0].name, 289 | this.config.breakpoints[this.config.breakpoints.length - 1].name, cb) 290 | } 291 | 292 | /** 293 | * Execute a callback starting at the given breakpoint 294 | * 295 | * @example 296 | * viewport.from("tablet", name => { 297 | * ... 298 | * }) 299 | * 300 | * @param first - First breakpoint name 301 | * @param cb - Callback to execute after resizing 302 | * 303 | * @return Promise resolving with no result 304 | */ 305 | public from>( 306 | first: string, cb: ViewportCallback 307 | ): Promise 308 | public from(first: string, cb: ViewportCallback): void 309 | public from(first: string, cb: ViewportCallback): void | Promise { 310 | return this.between(first, 311 | this.config.breakpoints[this.config.breakpoints.length - 1].name, cb) 312 | } 313 | 314 | /** 315 | * Execute a callback ending at the given breakpoint 316 | * 317 | * @example 318 | * viewport.to("tablet", name => { 319 | * ... 320 | * }) 321 | * 322 | * @param last - Last breakpoint name 323 | * @param cb - Callback to execute after resizing 324 | * 325 | * @return Promise resolving with no result 326 | */ 327 | public to>( 328 | last: string, cb: ViewportCallback 329 | ): Promise 330 | public to(last: string, cb: ViewportCallback): void 331 | public to(last: string, cb: ViewportCallback): void | Promise { 332 | return this.between(this.config.breakpoints[0].name, last, cb) 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": "#context", 3 | "breakpoints": [] 4 | } 5 | -------------------------------------------------------------------------------- /src/config/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema", 3 | "type": "object", 4 | "required": [ 5 | "context", 6 | "breakpoints" 7 | ], 8 | "properties": { 9 | "context": { 10 | "description": "Context element selector", 11 | "type": "string" 12 | }, 13 | "breakpoints": { 14 | "description": "Breakpoints", 15 | "type": "array", 16 | "items": { 17 | "type": "object", 18 | "required": [ 19 | "name", 20 | "size" 21 | ], 22 | "properties": { 23 | "name": { 24 | "description": "Breakpoint name", 25 | "type": "string" 26 | }, 27 | "size": { 28 | "description": "Breakpoint viewport", 29 | "type": "object", 30 | "required": [ 31 | "width", 32 | "height" 33 | ], 34 | "properties": { 35 | "width": { 36 | "description": "Viewport width", 37 | "type": "integer" 38 | }, 39 | "height": { 40 | "description": "Viewport height", 41 | "type": "integer" 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import * as fs from "fs" 24 | import * as path from "path" 25 | import * as url from "url" 26 | 27 | import { 28 | IncomingMessage, 29 | ServerResponse 30 | } from "http" 31 | import { validate } from "jsonschema" 32 | import { 33 | ClientOptions, 34 | ConfigOptions, 35 | FilePattern, 36 | Injectable 37 | } from "karma" 38 | 39 | import { 40 | Viewport, 41 | ViewportConfiguration 42 | } from "./adapter/viewport" 43 | import * as schema from "./config/schema.json" 44 | 45 | /* ---------------------------------------------------------------------------- 46 | * Types 47 | * ------------------------------------------------------------------------- */ 48 | 49 | /** 50 | * Augment Karma configuration type 51 | * 52 | * This is the exported configuration type for usage within Karma, because the 53 | * context selector and breakpoints are optional (merged with defaults). 54 | */ 55 | declare module "karma" { 56 | interface ConfigOptions { 57 | viewport?: Partial 58 | } 59 | } 60 | 61 | /** 62 | * Declare ambient viewport instance 63 | */ 64 | declare global { 65 | const viewport: Viewport 66 | } 67 | 68 | /* ---------------------------------------------------------------------------- 69 | * Functions 70 | * ------------------------------------------------------------------------- */ 71 | 72 | /** 73 | * Return specification for file server/watcher 74 | * 75 | * @param file - File 76 | * @param included - Whether the file should be included 77 | * 78 | * @return File pattern 79 | */ 80 | const pattern = (file: string, included = true): FilePattern => ({ 81 | pattern: file, 82 | included, 83 | served: true, 84 | watched: false 85 | }) 86 | 87 | /** 88 | * Setup framework configuration 89 | * 90 | * @param config - Karma configuration 91 | */ 92 | const framework: Injectable = (config: ConfigOptions) => { 93 | config.files!.push( 94 | pattern(path.resolve(__dirname, "config/default.json")), 95 | pattern(path.resolve(__dirname, "adapter/index.js")), 96 | pattern(path.resolve(__dirname, "adapter/index.js.map"), false) 97 | ) 98 | 99 | /* Register debug context middleware */ 100 | config.beforeMiddleware = config.beforeMiddleware || [] 101 | config.beforeMiddleware.push("viewport") 102 | 103 | /* Register preprocessor for viewport configuration */ 104 | config.preprocessors = config.preprocessors || {} 105 | config.preprocessors[ 106 | path.resolve(__dirname, "config/default.json") 107 | ] = ["viewport"] 108 | } 109 | 110 | /* Dependency injection */ 111 | framework.$inject = ["config"] 112 | 113 | /** 114 | * Initialize and configure middleware that runs before Karma's middleware 115 | * 116 | * By default, Karma's own context iframe is used for the viewport logic, but 117 | * the debug context doesn't include an iframe by default. If the requested 118 | * file is just the plain `debug.html` without the `embed` parameter (which is 119 | * introduced by this library) we just serve our monkey patched context iframe 120 | * including the actual debug context, served by Karma's own file server. 121 | * 122 | * The %X_UA_COMPATIBLE% placeholder must be replaced with the respective query 123 | * parameter, as Karma somehow relies on it. 124 | * 125 | * @return Connect-compatible middleware 126 | */ 127 | const middleware: Injectable = () => 128 | (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => void) => { 129 | const uri = url.parse(req.url!, true) 130 | if (uri.pathname !== "/debug.html" || 131 | typeof uri.query.embed !== "undefined") 132 | return next() 133 | 134 | /* Serve the surrounding debug context */ 135 | const debug = path.resolve(__dirname, "static/debug.html") 136 | fs.readFile(debug, (err, data) => { 137 | if (err) 138 | return next(err) 139 | 140 | /* Replace placeholder (copied from Karma's source) and serve */ 141 | res.writeHead(200, { "Content-Type": "text/html" }) 142 | res.end(data.toString() 143 | .replace("%X_UA_COMPATIBLE%", 144 | ''), "utf-8") 147 | }) 148 | } 149 | 150 | /** 151 | * Inject custom configuration 152 | * 153 | * @param viewport - Viewport configuration 154 | * @param client - Client configuration 155 | * 156 | * @return Preprocessor function 157 | */ 158 | const preprocessor: Injectable = 159 | (viewport: ViewportConfiguration, client: ClientOptions) => { 160 | if (viewport && typeof viewport !== "object") 161 | throw new TypeError(`Invalid viewport configuration: ${viewport}`) 162 | 163 | /* Return preprocessor function */ 164 | type DoneFn = (result: string) => void 165 | return (content: string, file: { path: string }, done: DoneFn) => { 166 | const config: ViewportConfiguration = 167 | Object.assign(JSON.parse(content), viewport) 168 | 169 | /* Validate viewport configuration */ 170 | const result = validate(config, schema) 171 | if (result.errors.length) 172 | throw new TypeError( 173 | `Invalid viewport configuration: ${result.errors[0].stack}`) 174 | 175 | /* Karma must run inside an iframe, if the context defaults */ 176 | if (config.context === "#context" && !client.useIframe) 177 | throw new Error("Invalid configuration: client.useIframe " + 178 | "must be set to true or a different context selector must be given") 179 | 180 | /* Ensure correct file type */ 181 | file.path += ".js" 182 | 183 | /* Store viewport configuration globally */ 184 | done(`window.__viewport__ = ${JSON.stringify(config, undefined, 2)}`) 185 | } 186 | } 187 | 188 | /* Dependency injection */ 189 | preprocessor.$inject = ["config.viewport", "config.client"] 190 | 191 | /* ---------------------------------------------------------------------------- 192 | * Export with ES5 compatibility 193 | * ------------------------------------------------------------------------- */ 194 | 195 | module.exports = { 196 | "framework:viewport": ["factory", framework], 197 | "middleware:viewport": ["factory", middleware], 198 | "preprocessor:viewport": ["factory", preprocessor] 199 | } 200 | -------------------------------------------------------------------------------- /src/static/debug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %X_UA_COMPATIBLE% 5 | Karma DEBUG RUNNER 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../tslint.json" 4 | ], 5 | "rules": { 6 | "array-type": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/config/browsers/integration.json: -------------------------------------------------------------------------------- 1 | { 2 | "chrome": { 3 | "base": "SauceLabs", 4 | "browserName": "chrome", 5 | "version": "latest", 6 | "platform": "Windows 10" 7 | }, 8 | "ie11": { 9 | "base": "SauceLabs", 10 | "browserName": "internet explorer", 11 | "version": "11", 12 | "platform": "Windows 10" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/config/browsers/unit.json: -------------------------------------------------------------------------------- 1 | { 2 | "chrome": { 3 | "base": "SauceLabs", 4 | "browserName": "chrome", 5 | "version": "latest", 6 | "platform": "Windows 10" 7 | }, 8 | "chrome1": { 9 | "base": "SauceLabs", 10 | "browserName": "chrome", 11 | "version": "latest-1", 12 | "platform": "Windows 10" 13 | }, 14 | "firefox": { 15 | "base": "SauceLabs", 16 | "browserName": "firefox", 17 | "version": "latest", 18 | "platform": "Windows 10" 19 | }, 20 | "firefox1": { 21 | "base": "SauceLabs", 22 | "browserName": "firefox", 23 | "version": "latest-1", 24 | "platform": "Windows 10" 25 | }, 26 | "edge15": { 27 | "base": "SauceLabs", 28 | "browserName": "MicrosoftEdge", 29 | "version": "15", 30 | "platform": "Windows 10" 31 | }, 32 | "edge14": { 33 | "base": "SauceLabs", 34 | "browserName": "MicrosoftEdge", 35 | "version": "14", 36 | "platform": "Windows 10" 37 | }, 38 | "edge13": { 39 | "base": "SauceLabs", 40 | "browserName": "MicrosoftEdge", 41 | "version": "13", 42 | "platform": "Windows 10" 43 | }, 44 | "ie11": { 45 | "base": "SauceLabs", 46 | "browserName": "internet explorer", 47 | "version": "11", 48 | "platform": "Windows 10" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/config/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import { 24 | Config as KarmaConfig, 25 | ConfigOptions as KarmaConfigOptions, 26 | } from "karma" 27 | import * as path from "path" 28 | import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin" 29 | import { 30 | Configuration as WebpackConfig, 31 | ProvidePlugin, 32 | RuleSetRule as WebpackRuleSetRule 33 | } from "webpack" 34 | 35 | /* ---------------------------------------------------------------------------- 36 | * Functions 37 | * ------------------------------------------------------------------------- */ 38 | 39 | /** 40 | * Webpack configuration 41 | * 42 | * @param config - Configuration 43 | * 44 | * @return Webpack configuration 45 | */ 46 | export function webpack( 47 | config: KarmaConfig & KarmaConfigOptions 48 | ): Partial { 49 | delete process.env.TS_NODE_PROJECT 50 | return { 51 | mode: "development", 52 | 53 | /* Loaders */ 54 | module: { 55 | rules: [ 56 | { 57 | test: /\.ts$/, 58 | use: ["babel-loader", "ts-loader"], 59 | exclude: /\/node_modules\// 60 | }, 61 | ...(config.singleRun 62 | ? [ 63 | ({ 64 | test: /\.ts$/, 65 | use: "istanbul-instrumenter-loader?+esModules", 66 | include: path.resolve(__dirname, "../../src"), 67 | enforce: "post" 68 | }) as WebpackRuleSetRule 69 | ] 70 | : []) 71 | ] 72 | }, 73 | resolve: { 74 | modules: [ 75 | path.resolve(__dirname, "../../node_modules") 76 | ], 77 | extensions: [".ts", ".js", ".json"], 78 | plugins: [ 79 | new TsconfigPathsPlugin() 80 | ], 81 | alias: { 82 | _: path.resolve(__dirname, "..") 83 | } 84 | }, 85 | plugins: [ 86 | 87 | /* Polyfills */ 88 | new ProvidePlugin({ 89 | Promise: "es6-promise" 90 | }) 91 | ], 92 | devtool: "source-map" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tests/fixtures/default.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /tests/helpers/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import { Chance } from "chance" 24 | 25 | /* ---------------------------------------------------------------------------- 26 | * Values 27 | * ------------------------------------------------------------------------- */ 28 | 29 | /** 30 | * Chance.js instance to generate random values 31 | */ 32 | export const chance = new Chance() 33 | -------------------------------------------------------------------------------- /tests/karma.conf.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import { 24 | Config as KarmaConfig, 25 | ConfigOptions as KarmaConfigOptions 26 | } from "karma" 27 | 28 | import { webpack } from "./config" 29 | 30 | /* ---------------------------------------------------------------------------- 31 | * Configuration 32 | * ------------------------------------------------------------------------- */ 33 | 34 | export default (config: KarmaConfig & KarmaConfigOptions) => { 35 | config.set({ 36 | basePath: __dirname, 37 | 38 | /* Frameworks to be used */ 39 | frameworks: [ 40 | "fixture", 41 | "jasmine" 42 | ], 43 | 44 | /* Include fixtures and tests */ 45 | files: [ 46 | "fixtures/**/*", 47 | "suites/unit/**/*.ts" 48 | ], 49 | 50 | /* Preprocessors */ 51 | preprocessors: { 52 | "**/*.html": ["html2js"], 53 | "**/*.ts": [ 54 | "webpack", 55 | "sourcemap" 56 | ] 57 | }, 58 | 59 | /* Webpack configuration */ 60 | webpack: webpack(config), 61 | 62 | /* Reporters */ 63 | reporters: config.singleRun 64 | ? ["summary", "coverage-istanbul"] 65 | : ["spec", "clear-screen"], 66 | 67 | /* Browsers */ 68 | browsers: ["ChromeNoSandbox", "FirefoxHeadless"], 69 | customLaunchers: { 70 | ChromeNoSandbox: { 71 | base: "ChromeHeadless", 72 | flags: [ 73 | "--disable-gpu", 74 | "--no-sandbox", 75 | "--disable-setuid-sandbox", 76 | "--disable-extensions", 77 | "--disable-dev-shm-usage" 78 | ] 79 | } 80 | }, 81 | 82 | /* Configuration for spec reporter */ 83 | specReporter: { 84 | suppressErrorSummary: true, 85 | suppressSkipped: !config.singleRun 86 | }, 87 | 88 | /* Configuration for coverage reporter */ 89 | coverageIstanbulReporter: { 90 | reports: ["html", "text", "lcovonly"] 91 | }, 92 | 93 | /* Hack: Don't serve TypeScript files with "video/mp2t" mime type */ 94 | mime: { 95 | "text/x-typescript": ["ts"] 96 | }, 97 | 98 | /* Client configuration */ 99 | client: { 100 | jasmine: { 101 | random: false 102 | } 103 | } 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /tests/karma.integration.conf.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | /// 24 | 25 | import { 26 | Config as KarmaConfig, 27 | ConfigOptions as KarmaConfigOptions 28 | } from "karma" 29 | 30 | import { webpack } from "./config" 31 | 32 | /* ---------------------------------------------------------------------------- 33 | * Configuration 34 | * ------------------------------------------------------------------------- */ 35 | 36 | export default (config: KarmaConfig & KarmaConfigOptions) => { 37 | config.set({ 38 | basePath: __dirname, 39 | 40 | /* Frameworks to be used */ 41 | frameworks: [ 42 | "jasmine", 43 | "viewport" 44 | ], 45 | 46 | /* Include tests */ 47 | files: [ 48 | "suites/integration/**/*.ts" 49 | ], 50 | 51 | /* Preprocessors */ 52 | preprocessors: { 53 | "**/*.ts": [ 54 | "webpack", 55 | "sourcemap" 56 | ] 57 | }, 58 | 59 | /* Register this plugin with Karma */ 60 | plugins: [ 61 | ...config.plugins!, 62 | require.resolve("..") 63 | ], 64 | 65 | /* Webpack configuration */ 66 | webpack: webpack(config), 67 | 68 | /* Reporters */ 69 | reporters: config.singleRun 70 | ? ["summary", "coverage-istanbul"] 71 | : ["spec", "clear-screen"], 72 | 73 | /* Browsers */ 74 | browsers: ["ChromeNoSandbox", "FirefoxHeadless"], 75 | customLaunchers: { 76 | ChromeNoSandbox: { 77 | base: "ChromeHeadless", 78 | flags: [ 79 | "--disable-gpu", 80 | "--no-sandbox", 81 | "--disable-setuid-sandbox", 82 | "--disable-extensions", 83 | "--disable-dev-shm-usage" 84 | ] 85 | } 86 | }, 87 | 88 | /* Configuration for spec reporter */ 89 | specReporter: { 90 | suppressErrorSummary: true, 91 | suppressSkipped: !config.singleRun 92 | }, 93 | 94 | /* Configuration for coverage reporter */ 95 | coverageIstanbulReporter: { 96 | reports: [] 97 | }, 98 | 99 | /* Hack: Don't serve TypeScript files with "video/mp2t" mime type */ 100 | mime: { 101 | "text/x-typescript": ["ts"] 102 | }, 103 | 104 | /* Client configuration */ 105 | client: { 106 | jasmine: { 107 | random: false 108 | } 109 | }, 110 | 111 | /* Viewport configuration */ 112 | viewport: { 113 | breakpoints: [ 114 | { 115 | name: "mobile", 116 | size: { 117 | width: 320, 118 | height: 480 119 | } 120 | }, 121 | { 122 | name: "tablet", 123 | size: { 124 | width: 768, 125 | height: 1024 126 | } 127 | }, 128 | { 129 | name: "screen", 130 | size: { 131 | width: 1440, 132 | height: 900 133 | } 134 | } 135 | ] 136 | } 137 | }) 138 | } 139 | -------------------------------------------------------------------------------- /tests/mocks/adapter/viewport.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import { ViewportConfiguration } from "adapter/viewport" 24 | 25 | import { chance } from "_/helpers" 26 | 27 | /* ---------------------------------------------------------------------------- 28 | * Values 29 | * ------------------------------------------------------------------------- */ 30 | 31 | /** 32 | * Viewport context selector 33 | */ 34 | const id = chance.string({ pool: "abcdefghijklmnopqrstuvwxyz" }) 35 | 36 | /* ---------------------------------------------------------------------------- 37 | * Mocks 38 | * ------------------------------------------------------------------------- */ 39 | 40 | /** 41 | * Mock viewport configuration 42 | * 43 | * @return Viewport configuration 44 | */ 45 | export function mockViewportConfiguration(): Readonly { 46 | return { 47 | context: `#${id}`, 48 | breakpoints: [ 49 | { 50 | name: "mobile", 51 | size: { 52 | width: 320, 53 | height: 480 54 | } 55 | }, 56 | { 57 | name: "tablet", 58 | size: { 59 | width: 768, 60 | height: 1024 61 | } 62 | }, 63 | { 64 | name: "screen", 65 | size: { 66 | width: 1440, 67 | height: 900 68 | } 69 | } 70 | ] 71 | } 72 | } 73 | 74 | /** 75 | * Mock viewport context 76 | * 77 | * @return Viewport context 78 | */ 79 | export function mockViewportContext(): Readonly { 80 | const context = document.createElement("iframe") 81 | context.id = id 82 | return context 83 | } 84 | -------------------------------------------------------------------------------- /tests/mocks/vendor/document.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | /* ---------------------------------------------------------------------------- 24 | * Mocks 25 | * ------------------------------------------------------------------------- */ 26 | 27 | /** 28 | * Mock document.querySelector returning the provided element or null 29 | * 30 | * @return document.querySelector spy 31 | */ 32 | export function mockQuerySelector(el: HTMLElement | null): jasmine.Spy { 33 | return spyOn(document, "querySelector") 34 | .and.returnValue(el) 35 | } 36 | -------------------------------------------------------------------------------- /tests/suites/integration/viewport.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import { chance } from "_/helpers" 24 | 25 | /* ---------------------------------------------------------------------------- 26 | * Tests 27 | * ------------------------------------------------------------------------- */ 28 | 29 | /* Viewport */ 30 | describe("Viewport", () => { 31 | 32 | /* Viewport context */ 33 | const context = window.parent.document.getElementById("context")! 34 | 35 | /* #set */ 36 | describe("#set", () => { 37 | 38 | /* Reset viewport after each test */ 39 | afterEach(() => { 40 | viewport.reset() 41 | }) 42 | 43 | /* Test: should set width */ 44 | it("should set width", () => { 45 | const width = chance.integer({ min: 100, max: 400 }) 46 | viewport.set(width) 47 | expect(context.style.width).toEqual(`${width}px`) 48 | expect(context.style.height).toEqual("") 49 | }) 50 | 51 | /* Test: should set width and height */ 52 | it("should set width and height", () => { 53 | const width = chance.integer({ min: 100, max: 400 }) 54 | const height = chance.integer({ min: 100, max: 400 }) 55 | viewport.set(width, height) 56 | expect(context.style.width).toEqual(`${width}px`) 57 | expect(context.style.height).toEqual(`${height}px`) 58 | }) 59 | 60 | /* Test: should set width and height of breakpoint */ 61 | it("should set width and height of breakpoint", () => { 62 | viewport.set("tablet") 63 | expect(context.style.width) 64 | .toEqual(`${viewport.config.breakpoints[1].size.width}px`) 65 | expect(context.style.height) 66 | .toEqual(`${viewport.config.breakpoints[1].size.height}px`) 67 | }) 68 | }) 69 | 70 | /* #reset */ 71 | describe("#reset", () => { 72 | 73 | /* Test: should reset width and height */ 74 | it("should reset width and height", () => { 75 | const width = chance.integer({ min: 100, max: 400 }) 76 | const height = chance.integer({ min: 100, max: 400 }) 77 | viewport.set(width, height) 78 | viewport.reset() 79 | expect(context.style.width).toEqual("") 80 | expect(context.style.height).toEqual("") 81 | }) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /tests/suites/unit/util/inspect.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import { inspect } from "adapter/util/inspect" 24 | 25 | import { chance } from "_/helpers" 26 | 27 | /* ---------------------------------------------------------------------------- 28 | * Tests 29 | * ------------------------------------------------------------------------- */ 30 | 31 | /* Utility functions */ 32 | describe("util/", () => { 33 | 34 | /* inspect */ 35 | describe("inspect", () => { 36 | 37 | /* Test: should handle undefined */ 38 | it("should handle undefined", () => { 39 | expect(inspect(undefined)).toEqual("undefined") 40 | }) 41 | 42 | /* Test: should handle null */ 43 | it("should handle null", () => { 44 | // tslint:disable-next-line no-null-keyword 45 | expect(inspect(null)).toEqual("null") 46 | }) 47 | 48 | /* Test: should handle number */ 49 | it("should handle number", () => { 50 | const value = chance.integer() 51 | expect(inspect(value)).toEqual(`${value}`) 52 | }) 53 | 54 | /* Test: should handle string */ 55 | it("should handle string", () => { 56 | const value = chance.string() 57 | expect(inspect(value)).toEqual(`'${value}'`) 58 | }) 59 | 60 | /* Test: should handle empty string */ 61 | it("should handle empty string", () => { 62 | expect(inspect("")).toEqual("''") 63 | }) 64 | 65 | /* Test: should handle object */ 66 | it("should handle object", () => { 67 | expect(inspect({ data: true })) 68 | .toEqual("{ \"data\": true }") 69 | }) 70 | 71 | /* Test: should handle function */ 72 | it("should handle function", () => { 73 | expect(inspect((x: any, y: any) => x + y)) 74 | .toEqual("function (x, y) { return x + y; }") 75 | }) 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /tests/suites/unit/viewport.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import { range, Viewport } from "adapter/viewport" 24 | 25 | import { chance } from "_/helpers" 26 | import { 27 | mockViewportConfiguration, 28 | mockViewportContext 29 | } from "_/mocks/adapter/viewport" 30 | import { mockQuerySelector } from "_/mocks/vendor/document" 31 | 32 | /* ---------------------------------------------------------------------------- 33 | * Tests 34 | * ------------------------------------------------------------------------- */ 35 | 36 | /* Viewport */ 37 | describe("Viewport", () => { 38 | 39 | /* Viewport configuration and context */ 40 | const config = mockViewportConfiguration() 41 | 42 | /* Viewport context */ 43 | let context: HTMLIFrameElement 44 | 45 | /* Setup fixtures */ 46 | beforeAll(() => { 47 | fixture.setBase("fixtures") 48 | }) 49 | 50 | /* Create and attach context */ 51 | beforeEach(() => { 52 | context = mockViewportContext() 53 | document.body.appendChild(context) 54 | 55 | /* Hack: Internet Explorer doesn't initialize the document for an empty 56 | iframe, so we have to do it by ourselves, see https://bit.ly/2GaF6Iw */ 57 | context.contentDocument!.write("") 58 | }) 59 | 60 | /* Detach context */ 61 | afterEach(() => { 62 | document.body.removeChild(context) 63 | }) 64 | 65 | /* range */ 66 | describe("range", () => { 67 | 68 | /* Test: should return first breakpoint */ 69 | it("should return first breakpoint", () => { 70 | const breakpoints = range(config.breakpoints, "mobile") 71 | expect(breakpoints).toEqual(jasmine.any(Array)) 72 | expect(breakpoints.length).toEqual(1) 73 | expect(breakpoints[0]).toEqual(config.breakpoints[0]) 74 | }) 75 | 76 | /* Test: should return middle breakpoint */ 77 | it("should return middle breakpoint", () => { 78 | const breakpoints = range(config.breakpoints, "tablet") 79 | expect(breakpoints).toEqual(jasmine.any(Array)) 80 | expect(breakpoints.length).toEqual(1) 81 | expect(breakpoints[0]).toEqual(config.breakpoints[1]) 82 | }) 83 | 84 | /* Test: should return last breakpoint */ 85 | it("should return last breakpoint", () => { 86 | const breakpoints = range(config.breakpoints, "screen") 87 | expect(breakpoints).toEqual(jasmine.any(Array)) 88 | expect(breakpoints.length).toEqual(1) 89 | expect(breakpoints[0]).toEqual(config.breakpoints[2]) 90 | }) 91 | 92 | /* Test: should return first to first breakpoint */ 93 | it("should return first to first breakpoint", () => { 94 | const breakpoints = range(config.breakpoints, "mobile", "mobile") 95 | expect(breakpoints).toEqual(jasmine.any(Array)) 96 | expect(breakpoints.length).toEqual(1) 97 | expect(breakpoints[0]).toEqual(config.breakpoints[0]) 98 | }) 99 | 100 | /* Test: should return first to last breakpoint */ 101 | it("should return first to last breakpoint", () => { 102 | const breakpoints = range(config.breakpoints, "mobile", "screen") 103 | expect(breakpoints).toEqual(jasmine.any(Array)) 104 | expect(breakpoints.length).toEqual(3) 105 | expect(breakpoints).toEqual(config.breakpoints) 106 | }) 107 | 108 | /* Test: should return last to last breakpoint */ 109 | it("should return last to last breakpoint", () => { 110 | const breakpoints = range(config.breakpoints, "screen", "screen") 111 | expect(breakpoints).toEqual(jasmine.any(Array)) 112 | expect(breakpoints.length).toEqual(1) 113 | expect(breakpoints[0]).toEqual(config.breakpoints[2]) 114 | }) 115 | 116 | /* Test: should throw on invalid breakpoint */ 117 | it("should throw on invalid breakpoint", () => { 118 | expect(() => { 119 | range([], "invalid") 120 | }).toThrow( 121 | new ReferenceError("Invalid breakpoint: 'invalid'")) 122 | }) 123 | }) 124 | 125 | /* #constructor */ 126 | describe("#constructor", () => { 127 | 128 | /* Test: should set configuration */ 129 | it("should set configuration", () => { 130 | const viewport = new Viewport(config, window) 131 | expect(viewport.config).toBe(config) 132 | }) 133 | 134 | /* Test: should resolve context */ 135 | it("should resolve context", () => { 136 | const querySelector = mockQuerySelector(context) 137 | const viewport = new Viewport(config, window) 138 | expect(viewport.context).toBe(context) 139 | expect(querySelector) 140 | .toHaveBeenCalledWith(config.context) 141 | }) 142 | 143 | /* Test: should throw on missing context */ 144 | it("should throw on missing context", () => { 145 | // tslint:disable-next-line no-null-keyword 146 | mockQuerySelector(null) 147 | expect(() => { 148 | new Viewport(config, window) 149 | }).toThrow(new TypeError(`No match for selector: '${config.context}'`)) 150 | }) 151 | }) 152 | 153 | /* #load */ 154 | describe("#load", () => { 155 | 156 | /* Test: should set context source */ 157 | it("should set context source", done => { 158 | const viewport = new Viewport(config, window) 159 | viewport.load("/debug.html", () => { 160 | expect(context.src).toContain("/debug.html") 161 | done() 162 | }) 163 | }) 164 | 165 | /* Test: should set context source and return promise */ 166 | it("should set context source and return promise", done => { 167 | const viewport = new Viewport(config, window) 168 | viewport.load("/debug.html") 169 | .then(() => { 170 | expect(context.src).toContain("/debug.html") 171 | done() 172 | }) 173 | }) 174 | }) 175 | 176 | /* #offset */ 177 | describe("#offset", () => { 178 | 179 | /* Test: should set horizontal offset */ 180 | it("should set horizontal offset", () => { 181 | const viewport = new Viewport(config, window) 182 | const x = chance.integer({ min: 10, max: 100 }) 183 | context.contentDocument!.body.style.width = 184 | `${context.contentWindow!.innerWidth + x}px` 185 | context.contentDocument!.body.style.height = 186 | `${context.contentWindow!.innerHeight}px` 187 | viewport.offset(x) 188 | expect(viewport.context.contentWindow!.pageXOffset).toEqual(x) 189 | }) 190 | 191 | /* Test: should set horizontal and vertical offset */ 192 | it("should set horizontal and vertical offset", () => { 193 | const viewport = new Viewport(config, window) 194 | const x = chance.integer({ min: 10, max: 100 }) 195 | const y = chance.integer({ min: 10, max: 100 }) 196 | context.contentDocument!.body.style.width = 197 | `${context.contentWindow!.innerWidth + x}px` 198 | context.contentDocument!.body.style.height = 199 | `${context.contentWindow!.innerHeight + y}px` 200 | viewport.offset(x, y) 201 | expect(viewport.context.contentWindow!.pageXOffset).toEqual(x) 202 | expect(viewport.context.contentWindow!.pageYOffset).toEqual(y) 203 | }) 204 | }) 205 | 206 | /* #set */ 207 | describe("#set", () => { 208 | 209 | /* Test: should set width */ 210 | it("should set width", () => { 211 | const viewport = new Viewport(config, window) 212 | const width = chance.integer({ min: 100, max: 400 }) 213 | viewport.set(width) 214 | expect(context.style.width).toEqual(`${width}px`) 215 | expect(context.style.height).toEqual("") 216 | }) 217 | 218 | /* Test: should set width and height */ 219 | it("should set width and height", () => { 220 | const viewport = new Viewport(config, window) 221 | const width = chance.integer({ min: 100, max: 400 }) 222 | const height = chance.integer({ min: 100, max: 400 }) 223 | viewport.set(width, height) 224 | expect(context.style.width).toEqual(`${width}px`) 225 | expect(context.style.height).toEqual(`${height}px`) 226 | }) 227 | 228 | /* Test: should set width and height of breakpoint */ 229 | it("should set width and height of breakpoint", () => { 230 | const viewport = new Viewport(config, window) 231 | viewport.set("tablet") 232 | expect(context.style.width) 233 | .toEqual(`${config.breakpoints[1].size.width}px`) 234 | expect(context.style.height) 235 | .toEqual(`${config.breakpoints[1].size.height}px`) 236 | }) 237 | 238 | /* Test: should force layout */ 239 | it("should force layout", () => { 240 | spyOn(context.contentDocument!.body, "getBoundingClientRect") 241 | const viewport = new Viewport(config, window) 242 | const width = chance.integer({ min: 100, max: 400 }) 243 | viewport.set(width) 244 | expect(context.contentDocument!.body.getBoundingClientRect) 245 | .toHaveBeenCalled() 246 | }) 247 | 248 | /* Test: should throw on invalid breakpoint */ 249 | it("should throw on invalid breakpoint", () => { 250 | const viewport = new Viewport(config, window) 251 | expect(() => { 252 | viewport.set("invalid") 253 | }).toThrow(new ReferenceError("Invalid breakpoint: 'invalid'")) 254 | }) 255 | 256 | /* Test: should throw on invalid width */ 257 | it("should throw on invalid width", () => { 258 | const viewport = new Viewport(config, window) 259 | const width = chance.integer({ min: -400, max: -100 }) 260 | expect(() => { 261 | viewport.set(width) 262 | }).toThrow(new TypeError(`Invalid breakpoint width: ${width}`)) 263 | }) 264 | 265 | /* Test: should throw on invalid width and height */ 266 | it("should throw on invalid width and height", () => { 267 | const viewport = new Viewport(config, window) 268 | const width = chance.integer({ min: -400, max: -100 }) 269 | const height = chance.integer({ min: -400, max: -100 }) 270 | expect(() => { 271 | viewport.set(width, height) 272 | }).toThrow(new TypeError(`Invalid breakpoint width: ${width}`)) 273 | }) 274 | 275 | /* Test: should throw on invalid width */ 276 | it("should throw on invalid height", () => { 277 | const viewport = new Viewport(config, window) 278 | const width = chance.integer({ min: 100, max: 400 }) 279 | const height = chance.integer({ min: -400, max: -100 }) 280 | expect(() => { 281 | viewport.set(width, height) 282 | }).toThrow(new TypeError(`Invalid breakpoint height: ${height}`)) 283 | }) 284 | }) 285 | 286 | /* #reset */ 287 | describe("#reset", () => { 288 | 289 | /* Test: should reset width and height */ 290 | it("should reset offset", () => { 291 | const viewport = new Viewport(config, window) 292 | const x = chance.integer({ min: 10, max: 100 }) 293 | const y = chance.integer({ min: 10, max: 100 }) 294 | context.contentDocument!.body.style.width = 295 | `${context.contentWindow!.innerWidth + x}` 296 | context.contentDocument!.body.style.height = 297 | `${context.contentWindow!.innerHeight + y}` 298 | viewport.offset(x, y) 299 | viewport.reset() 300 | expect(viewport.context.contentWindow!.pageXOffset).toEqual(0) 301 | expect(viewport.context.contentWindow!.pageYOffset).toEqual(0) 302 | }) 303 | 304 | /* Test: should reset width and height */ 305 | it("should reset width and height", () => { 306 | const viewport = new Viewport(config, window) 307 | const width = chance.integer({ min: 100, max: 400 }) 308 | const height = chance.integer({ min: 100, max: 400 }) 309 | viewport.set(width, height) 310 | viewport.reset() 311 | expect(context.style.width).toEqual("") 312 | expect(context.style.height).toEqual("") 313 | }) 314 | 315 | /* Test: should force layout */ 316 | it("should force layout", () => { 317 | spyOn(context.contentDocument!.body, "getBoundingClientRect") 318 | const viewport = new Viewport(config, window) 319 | viewport.reset() 320 | expect(context.contentDocument!.body.getBoundingClientRect) 321 | .toHaveBeenCalled() 322 | }) 323 | }) 324 | 325 | /* #between */ 326 | describe("#between", () => { 327 | 328 | /* Test: should invoke callback on breakpoints */ 329 | it("should invoke callback on breakpoints", () => { 330 | const cb = jasmine.createSpy("callback") 331 | const viewport = new Viewport(config, window) 332 | viewport.between("mobile", "tablet", cb) 333 | expect(cb).toHaveBeenCalledWith("mobile") 334 | expect(cb).toHaveBeenCalledWith("tablet") 335 | expect(cb).not.toHaveBeenCalledWith("screen") 336 | }) 337 | 338 | /* Test: should invoke callback returning promise on breakpoints */ 339 | it("should invoke callback returning promise on breakpoints", async () => { 340 | const cb = jasmine.createSpy("callback") 341 | .and.returnValue(Promise.resolve()) 342 | const viewport = new Viewport(config, window) 343 | await viewport.between("tablet", "screen", cb) 344 | expect(cb).not.toHaveBeenCalledWith("mobile") 345 | expect(cb).toHaveBeenCalledWith("tablet") 346 | expect(cb).toHaveBeenCalledWith("screen") 347 | }) 348 | }) 349 | 350 | /* #each */ 351 | describe("#each", () => { 352 | 353 | /* Load fixtures */ 354 | beforeEach(() => { 355 | fixture.load("default.html") 356 | }) 357 | 358 | /* Test: should invoke callback on breakpoints */ 359 | it("should invoke callback on breakpoints", () => { 360 | const cb = jasmine.createSpy("callback") 361 | const viewport = new Viewport(config, window) 362 | viewport.each(cb) 363 | expect(cb).toHaveBeenCalledWith("mobile") 364 | expect(cb).toHaveBeenCalledWith("tablet") 365 | expect(cb).toHaveBeenCalledWith("screen") 366 | }) 367 | 368 | /* Test: should set width and height of breakpoint */ 369 | it("should set width and height of breakpoint", () => { 370 | const cb = jasmine.createSpy("callback") 371 | .and.callFake((name: string) => { 372 | const style = window.getComputedStyle(document.body) 373 | switch (name) { 374 | case "mobile": expect(style.fontSize).toEqual("16px"); break 375 | case "tablet": expect(style.fontSize).toEqual("14px"); break 376 | case "screen": expect(style.fontSize).toEqual("12px"); break 377 | } 378 | }) 379 | const viewport = new Viewport({ ...config, context: "#context" }, window) 380 | viewport.each(cb) 381 | expect(cb.calls.count()).toEqual(3) 382 | }) 383 | }) 384 | 385 | /* #from */ 386 | describe("#from", () => { 387 | 388 | /* Test: should invoke callback on breakpoints */ 389 | it("should invoke callback on breakpoints", () => { 390 | const cb = jasmine.createSpy("callback") 391 | const viewport = new Viewport(config, window) 392 | viewport.from("tablet", cb) 393 | expect(cb).not.toHaveBeenCalledWith("mobile") 394 | expect(cb).toHaveBeenCalledWith("tablet") 395 | expect(cb).toHaveBeenCalledWith("screen") 396 | }) 397 | }) 398 | 399 | /* #to */ 400 | describe("#to", () => { 401 | 402 | /* Test: should invoke callback on breakpoints */ 403 | it("should invoke callback on breakpoints", () => { 404 | const cb = jasmine.createSpy("callback") 405 | const viewport = new Viewport(config, window) 406 | viewport.to("tablet", cb) 407 | expect(cb).toHaveBeenCalledWith("mobile") 408 | expect(cb).toHaveBeenCalledWith("tablet") 409 | expect(cb).not.toHaveBeenCalledWith("screen") 410 | }) 411 | }) 412 | }) 413 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "_/*": ["../tests/*"] 6 | }, 7 | "rootDir": "..", 8 | "target": "es5" 9 | }, 10 | "include": [ 11 | "../src", 12 | "../tests", 13 | "../typings" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tests/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../tslint.json" 4 | ], 5 | "rules": { 6 | "no-object-literal-type-assertion": false, 7 | "no-unused-expression": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "baseUrl": "src", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "declarationDir": "dist", 8 | "lib": [ 9 | "dom", 10 | "es2015", 11 | "esnext" 12 | ], 13 | "module": "commonjs", 14 | "moduleResolution": "node", 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "outDir": "dist", 22 | "removeComments": false, 23 | "resolveJsonModule": true, 24 | "sourceMap": true, 25 | "strictBindCallApply": true, 26 | "strictFunctionTypes": true, 27 | "strictNullChecks": true, 28 | "strictPropertyInitialization": true, 29 | "stripInternal": true, 30 | "target": "es5" 31 | }, 32 | "files": [ 33 | "src/index.ts" 34 | ], 35 | "include": [ 36 | "typings" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest" 4 | ], 5 | "rules": { 6 | "arrow-parens": [ 7 | true, 8 | "ban-single-arg-parens" 9 | ], 10 | "ban": [ 11 | true, 12 | ["fit"], 13 | ["fdescribe"], 14 | ["xit"], 15 | ["xdescribe"], 16 | { 17 | "name": "Object.assign", 18 | "message": "Use the spread operator instead." 19 | } 20 | ], 21 | "class-name": true, 22 | "comment-format": [ 23 | true, 24 | "check-space" 25 | ], 26 | "curly": false, 27 | "indent": [ 28 | true, 29 | "spaces" 30 | ], 31 | "interface-name": false, 32 | "jsdoc-format": true, 33 | "no-implicit-dependencies": false, 34 | "no-internal-module": true, 35 | "no-null-keyword": true, 36 | "no-reference": false, 37 | "no-submodule-imports": false, 38 | "no-trailing-whitespace": true, 39 | "no-var-keyword": true, 40 | "object-literal-key-quotes": [ 41 | true, 42 | "consistent-as-needed" 43 | ], 44 | "object-literal-sort-keys": false, 45 | "one-line": [ 46 | true, 47 | "check-open-brace", 48 | "check-whitespace" 49 | ], 50 | "prefer-const": true, 51 | "quotemark": [ 52 | true, 53 | "double", 54 | "avoid-escape" 55 | ], 56 | "semicolon": [ 57 | true, 58 | "never" 59 | ], 60 | "trailing-comma": [ 61 | true, 62 | { 63 | "multiline": { 64 | "objects": "never", 65 | "arrays": "never", 66 | "functions": "never", 67 | "typeLiterals": "never" 68 | }, 69 | "esSpecCompliant": true 70 | } 71 | ], 72 | "typedef-whitespace": [ 73 | true, 74 | { 75 | "call-signature": "nospace", 76 | "index-signature": "nospace", 77 | "parameter": "nospace", 78 | "property-declaration": "nospace", 79 | "variable-declaration": "nospace" 80 | }, 81 | { 82 | "call-signature": "onespace", 83 | "index-signature": "onespace", 84 | "parameter": "onespace", 85 | "property-declaration": "onespace", 86 | "variable-declaration": "onespace" 87 | } 88 | ], 89 | "variable-name": [ 90 | true, 91 | "allow-leading-underscore", 92 | "ban-keywords", 93 | "check-format" 94 | ], 95 | "whitespace": [ 96 | true, 97 | "check-branch", 98 | "check-decl", 99 | "check-operator", 100 | "check-module", 101 | "check-separator", 102 | "check-type" 103 | ] 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /typings/jsonschema.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import "jsonschema" 24 | 25 | declare module "jsonschema" { 26 | interface ValidationError { 27 | stack: string 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /typings/karma.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import "karma" 24 | 25 | import { Configuration as WebpackConfig } from "webpack" 26 | 27 | declare module "karma" { 28 | interface ConfigOptions { 29 | beforeMiddleware?: string[] 30 | webpack?: Partial /* karma-webpack */ 31 | specReporter?: { /* karma-spec-reporter */ 32 | suppressErrorSummary?: boolean 33 | suppressPassed?: boolean 34 | suppressSkipped?: boolean 35 | } 36 | coverageIstanbulReporter?: { /* karma-coverage */ 37 | reports: string[] 38 | } 39 | sauceLabs?: { 40 | build?: string, 41 | testName: string, 42 | recordVideo: boolean, 43 | recordScreenshots: boolean 44 | } 45 | } 46 | interface ClientOptions { /* karma-jasmine */ 47 | jasmine?: { 48 | random: boolean 49 | } 50 | } 51 | interface Injectable { 52 | (...args: any[]): void 53 | $inject?: string[] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /typings/project-name-generator.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | declare module "project-name-generator" { 24 | export function generate(): { 25 | raw: string, 26 | dashed: string, 27 | spaced: string 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2020 Martin Donath 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | import * as path from "path" 24 | import { 25 | Configuration, 26 | NoEmitOnErrorsPlugin, 27 | ProvidePlugin 28 | } from "webpack" 29 | 30 | /* ---------------------------------------------------------------------------- 31 | * Configuration 32 | * ------------------------------------------------------------------------- */ 33 | 34 | /** 35 | * Webpack configuration 36 | * 37 | * @param env - Webpack environment arguments 38 | * @param args - Command-line arguments 39 | * 40 | * @return Webpack configuration 41 | */ 42 | export default (_env: never, args: Configuration) => { 43 | const config: Configuration = { 44 | mode: args.mode, 45 | 46 | /* Entrypoint */ 47 | entry: ["src/adapter"], 48 | 49 | /* Loaders */ 50 | module: { 51 | rules: [ 52 | 53 | /* TypeScript */ 54 | { 55 | test: /\.ts$/, 56 | use: [ 57 | "babel-loader", 58 | "ts-loader" 59 | ], 60 | exclude: /\/node_modules\// 61 | } 62 | ] 63 | }, 64 | 65 | /* Export class constructor as entrypoint */ 66 | output: { 67 | path: path.resolve(__dirname, "dist/adapter"), 68 | pathinfo: false, 69 | filename: "index.js", 70 | libraryTarget: "window" 71 | }, 72 | 73 | /* Plugins */ 74 | plugins: [ 75 | 76 | /* Don't emit assets if there were errors */ 77 | new NoEmitOnErrorsPlugin(), 78 | 79 | /* Polyfills */ 80 | new ProvidePlugin({ 81 | Promise: "es6-promise" 82 | }) 83 | ], 84 | 85 | /* Module resolver */ 86 | resolve: { 87 | modules: [ 88 | __dirname, 89 | path.resolve(__dirname, "node_modules") 90 | ], 91 | extensions: [".ts", ".js", ".json"] 92 | }, 93 | 94 | /* Sourcemaps */ 95 | devtool: "source-map" 96 | } 97 | 98 | /* We're good to go */ 99 | return config 100 | } 101 | --------------------------------------------------------------------------------