├── .editorconfig ├── .github └── workflows │ └── skyux-azboards.yml ├── .gitignore ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── .nvmrc ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── appveyor.yml ├── cli ├── build-public-library.js ├── build.js ├── e2e.js ├── generate.js ├── lint.js ├── pact.js ├── serve.js ├── test.js ├── utils │ ├── browser.js │ ├── config-resolver.js │ ├── prepare-library-package.js │ ├── run-build.js │ ├── run-compiler.js │ ├── server.js │ ├── stage-library-ts.js │ └── ts-linter.js └── version.js ├── config ├── axe │ └── axe.config.js ├── karma │ ├── dev-runtime.karma.conf.js │ ├── dev-src-app.karma.conf.js │ ├── pact.karma.conf.js │ ├── shared.karma.conf.js │ ├── test.karma.conf.js │ └── watch.karma.conf.js ├── protractor │ ├── protractor-dev.conf.js │ └── protractor.conf.js ├── sky-pages │ └── sky-pages.config.js └── webpack │ ├── alias-builder.js │ ├── build-aot.webpack.config.js │ ├── build-public-library.webpack.config.js │ ├── build.webpack.config.js │ ├── common.webpack.config.js │ ├── serve.webpack.config.js │ └── test.webpack.config.js ├── e2e ├── shared │ ├── cli.js │ ├── common.js │ └── tests.js ├── skyux-build-aot.e2e-spec.js ├── skyux-build-jit.e2e-spec.js ├── skyux-e2e.e2e-spec.js ├── skyux-lib-help-tests │ ├── fixtures │ │ └── skyux-modal │ │ │ ├── modal-form-fixture.component.ts │ │ │ └── modal-launcher-fixture.component.ts │ └── skyux-lib-help.e2e-spec.js ├── skyux-serve.e2e-spec.js └── skyux-test.e2e-spec.js ├── index.d.ts ├── index.js ├── jasmine.json ├── lib ├── assets-processor.js ├── locale-assets-processor.js ├── plugin-file-processor.js ├── sky-pages-assets-generator.js ├── sky-pages-component-generator.js ├── sky-pages-module-generator.js └── sky-pages-route-generator.js ├── loader ├── sky-app-config │ └── index.js ├── sky-assets │ └── index.js ├── sky-pages-module │ └── index.js └── sky-processor │ ├── index.js │ ├── postload.js │ └── preload.js ├── package.json ├── plugin ├── output-keep-alive │ └── index.js └── save-metadata │ └── index.js ├── runtime ├── assets.service.ts ├── auth-http.ts ├── auth-token-provider.ts ├── bootstrapper.spec.ts ├── bootstrapper.ts ├── config-params.ts ├── config.ts ├── directives │ ├── index.ts │ ├── sky-app-link-external.directive.ts │ └── sky-app-link.directive.ts ├── format.ts ├── i18n │ ├── host-locale-provider.spec.ts │ ├── host-locale-provider.ts │ ├── index.ts │ ├── locale-info.ts │ ├── locale-provider.ts │ ├── resources.pipe.ts │ ├── resources.service.ts │ └── resources.ts ├── index.ts ├── omnibar-provider.ts ├── omnibar-ready-args.ts ├── pact-auth-token-provider.ts ├── pact.service.ts ├── params.ts ├── runtime.module.ts ├── search-results-provider.ts ├── style-loader.ts ├── testing │ ├── browser │ │ ├── i18n │ │ │ └── resources-test.service.ts │ │ ├── index.ts │ │ ├── matchers.ts │ │ ├── test-module.ts │ │ ├── test-utility-dom-event-options.ts │ │ └── test-utility.ts │ └── e2e │ │ ├── a11y.ts │ │ ├── host-browser.ts │ │ └── index.ts ├── viewport.service.ts └── window-ref.ts ├── skyuxconfig.json ├── src ├── app │ ├── app-extras.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ └── sky-pages.module.ts ├── assets │ └── locales │ │ └── resources_en_US.json ├── main-internal.aot.ts ├── main-internal.ts ├── main.ejs ├── main.ts ├── polyfills.ts ├── skyux.ts └── vendor.ts ├── ssl ├── server.crt ├── server.key └── skyux-ca.crt ├── test ├── assets-utils.spec.js ├── cli-build-public-library.spec.js ├── cli-build.spec.js ├── cli-e2e.spec.js ├── cli-generate.spec.js ├── cli-lint.spec.js ├── cli-pact.spec.js ├── cli-serve.spec.js ├── cli-test.spec.js ├── cli-utils-browser.spec.js ├── cli-utils-config-resolver.spec.js ├── cli-utils-prepare-library-package.spec.js ├── cli-utils-run-build.spec.js ├── cli-utils-run-compiler.spec.js ├── cli-utils-server.spec.js ├── cli-utils-stage-library-ts.spec.js ├── cli-utils-ts-linter.spec.js ├── cli-version.spec.js ├── config-axe.spec.js ├── config-karma-pact.spec.js ├── config-karma-shared.spec.js ├── config-karma-test.spec.js ├── config-protractor.spec.js ├── config-sky-pages.spec.js ├── config-webpack-build-aot.spec.js ├── config-webpack-build-common.spec.js ├── config-webpack-build-public-library.spec.js ├── config-webpack-build.spec.js ├── config-webpack-common.spec.js ├── config-webpack-serve.spec.js ├── config-webpack-test.spec.js ├── fixtures │ └── public │ │ └── sky-pages-component-generator.fixture.component.ts ├── index-ejs.spec.js ├── index.spec.js ├── lib-assets-processor.spec.js ├── lib-locale-assets-processor.spec.js ├── loader-assets.spec.js ├── loader-module.spec.js ├── loader-processor.spec.js ├── plugin-file-processor.spec.js ├── plugin-output-keep-alive.spec.js ├── sky-pages-assets-generator.spec.js ├── sky-pages-component-generator.spec.js ├── sky-pages-module-generator.spec.js ├── sky-pages-route-generator.spec.js ├── utils-host-utils.spec.js ├── utils-merge-utils.spec.js └── utils-pact-servers.spec.js ├── tsconfig.json ├── tslint.json └── utils ├── assets-utils.js ├── cli-test.js ├── codegen-utils.js ├── host-utils.js ├── merge.js ├── pact-servers.js ├── runtime-test-skyux.css ├── runtime-test-utils.js ├── spec-bundle.js └── spec-styles.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # @AngularClass 2 | # http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | insert_final_newline = false 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.github/workflows/skyux-azboards.yml: -------------------------------------------------------------------------------- 1 | name: Sync issue to Azure DevOps work item 2 | 3 | "on": 4 | issues: 5 | types: 6 | [deleted, closed, reopened, labeled, unlabeled] 7 | 8 | jobs: 9 | sync: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Block Concurrent Executions 13 | uses: softprops/turnstyle@v1 14 | with: 15 | poll-interval-seconds: 30 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} 18 | - name: Print context 19 | env: 20 | EVENT_PAYLOAD: ${{ toJson(github.event) }} 21 | run: | 22 | echo "$EVENT_PAYLOAD" 23 | - uses: danhellem/github-actions-issue-to-work-item@master 24 | if: contains(github.event.issue.labels.*.name, env.TEST_VALUE) 25 | env: 26 | TEST_VALUE: "Type: Bug" 27 | ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}" 28 | github_token: "${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" 29 | ado_organization: "${{ secrets.ADO_ORGANIZATION }}" 30 | ado_project: "${{ secrets.ADO_PROJECT }}" 31 | ado_area_path: "${{ secrets.ADO_AREA_PATH }}" 32 | ado_wit: "Bug" 33 | ado_new_state: "New" 34 | ado_close_state: "Closed" 35 | ado_bypassrules: true 36 | - uses: danhellem/github-actions-issue-to-work-item@master 37 | if: contains(github.event.issue.labels.*.name, env.TEST_VALUE) 38 | env: 39 | TEST_VALUE: "Type: Enhancement" 40 | ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}" 41 | github_token: "${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" 42 | ado_organization: "${{ secrets.ADO_ORGANIZATION }}" 43 | ado_project: "${{ secrets.ADO_PROJECT }}" 44 | ado_area_path: "${{ secrets.ADO_AREA_PATH }}" 45 | ado_wit: "User Story" 46 | ado_new_state: "New" 47 | ado_close_state: "Closed" 48 | ado_bypassrules: true 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | coverage-runtime 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | .DS_Store 41 | .e2e-tmp 42 | yarn.lock 43 | package-lock.json 44 | 45 | # IntelliJ IDEA files 46 | .idea/ 47 | *.iml 48 | *.ipr 49 | *.iws 50 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "airbnb", 3 | "disallowDanglingUnderscores": null, 4 | "disallowTrailingWhitespace": null, 5 | "disallowQuotedKeysInObjects": null, 6 | "requireLineFeedAtFileEnd": null, 7 | "requireTrailingComma": null, 8 | "requirePaddingNewLinesBeforeLineComments": null, 9 | "validateLineBreaks": null, 10 | "excludeFiles": [ 11 | "coverage/**" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "forin": true, 6 | "freeze": true, 7 | "noarg": true, 8 | "nonbsp": true, 9 | "nonew": true, 10 | "strict": true, 11 | "undef": true, 12 | "unused": true 13 | } 14 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/carbon 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: required 3 | dist: trusty 4 | 5 | env: 6 | - TEST_COMMAND=coverage 7 | - TEST_COMMAND=e2e 8 | 9 | node_js: 10 | - "8" 11 | - "6" 12 | 13 | script: "npm run lint && npm run $TEST_COMMAND" 14 | 15 | addons: 16 | apt: 17 | sources: 18 | - google-chrome 19 | packages: 20 | - google-chrome-stable 21 | 22 | branches: 23 | only: 24 | - master 25 | - /^rc-.*$/ 26 | - /^[0-9]+\.[0-9]+\.[0-9]+.*/ 27 | 28 | before_install: 29 | - sudo cp ./ssl/skyux-ca.crt /usr/local/share/ca-certificates/skyux-ca.crt 30 | - sudo update-ca-certificates 31 | 32 | before_script: 33 | - "export CHROME_BIN=chromium-browser" 34 | - "export DISPLAY=:99.0" 35 | - "sh -e /etc/init.d/xvfb start" 36 | - sleep 3 # give xvfb some time to start 37 | 38 | after_success: 39 | - bash <(curl -s https://codecov.io/bash) -s coverage/builder -F builder 40 | - bash <(curl -s https://codecov.io/bash) -s coverage/runtime/ -F runtime 41 | - bash <(curl -s https://codecov.io/bash) -s coverage/src-app/ -F srcapp 42 | 43 | # Run the deployment after all jobs have been successfully completed. 44 | # https://docs.travis-ci.com/user/build-stages 45 | jobs: 46 | include: 47 | - stage: deploy 48 | env: TEST_COMMAND=none 49 | script: bash <(curl -s https://blackbaud.github.io/skyux-travis/after-success.sh) 50 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug SKY UX Builder", 6 | "type": "node2", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/../skyux-cli/bin/skyux.js", 9 | "stopOnEntry": false, 10 | "args": ["build"], 11 | "cwd": "${workspaceRoot}", 12 | "preLaunchTask": null, 13 | "runtimeExecutable": null, 14 | "runtimeArgs": [ 15 | "--nolazy" 16 | ], 17 | "env": { 18 | "NODE_ENV": "development" 19 | }, 20 | "console": "integratedTerminal", 21 | "sourceMaps": false, 22 | "outFiles": [] 23 | }, 24 | { 25 | "name": "Run jasmine tests", 26 | "type": "node2", 27 | "request": "launch", 28 | "program": "${workspaceRoot}/node_modules/.bin/jasmine", 29 | "stopOnEntry": false, 30 | "args": ["JASMINE_CONFIG_PATH=jasmine.json"], 31 | "cwd": "${workspaceRoot}", 32 | "preLaunchTask": null, 33 | "runtimeExecutable": null, 34 | "runtimeArgs": [ 35 | "--nolazy" 36 | ], 37 | "env": { 38 | "NODE_ENV": "development" 39 | }, 40 | "console": "integratedTerminal", 41 | "sourceMaps": false, 42 | "outFiles": [] 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Columns at which to show vertical rulers 4 | "editor.rulers": [100], 5 | 6 | // Controls after how many characters the editor will wrap to the next line. Setting this to 0 turns on viewport width wrapping (word wrapping). Setting this to -1 forces the editor to never wrap. 7 | "editor.wordWrap": "wordWrapColumn", 8 | "editor.wordWrapColumn": 100, 9 | 10 | "typescript.tsdk": "node_modules/typescript/lib" 11 | } 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to SKY UX Builder 2 | 3 | - `npm run coverage` Runs our unit spec tests, saving coverage to the `coverage/` directory. This contains the following sub tasks: 4 | - `npm run coverage:builder` which tests the nodejs/builder code and places coverage in `coverage/builder`. 5 | - `npm run coverage:runtime` which tests the `runtime` components and places coverage in `coverage/runtime`. 6 | - `npm run coverage:src-app` which tests the `src/app` components and places coverage in `coverage/src-app`. 7 | - `npm run e2e` Runs the defined end-to-end tests. 8 | - `npm run jscs` Runs this code against the jscs linter. 9 | - `npm run jshint` Runs this code against the jshint linter. 10 | - `npm run lint` Runs the `jscs` and `jshint` commands. 11 | - `npm run test` Runs ALL test commands: `lint`, `coverage`, and `e2e`. 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Blackbaud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @blackbaud/skyux-builder 2 | 3 | [![npm](https://img.shields.io/npm/v/@blackbaud/skyux-builder.svg)](https://www.npmjs.com/package/@blackbaud/skyux-builder) 4 | [![status](https://travis-ci.org/blackbaud/skyux-builder.svg?branch=master)](https://travis-ci.org/blackbaud/skyux-builder) 5 | 6 | Builds the output of a SKY UX application. See [skyux-cli](https://github.com/blackbaud/skyux-cli) for more information. 7 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | 5 | environment: 6 | nodejs_version: "6" 7 | 8 | install: 9 | - choco install googlechrome --ignore-checksums 10 | - ps: Install-Product node $env:nodejs_version 11 | - npm install 12 | 13 | test_script: 14 | - node --version 15 | - npm --version 16 | - npm test 17 | 18 | # Don't actually build. 19 | build: off 20 | -------------------------------------------------------------------------------- /cli/build.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const logger = require('@blackbaud/skyux-logger'); 5 | const runBuild = require('./utils/run-build'); 6 | 7 | /** 8 | * Executes the build command. 9 | * @name build 10 | * @param {*} skyPagesConfig 11 | * @param {*} webpack 12 | * @param {*} isAot 13 | * @param {*} cancelProcessExit 14 | */ 15 | function build(argv, skyPagesConfig, webpack) { 16 | return runBuild(argv, skyPagesConfig, webpack) 17 | .then((stats) => { 18 | logger.info('Build successfully completed.'); 19 | return stats; 20 | }) 21 | .catch(err => { 22 | logger.error(err); 23 | process.exit(1); 24 | }); 25 | } 26 | 27 | module.exports = build; 28 | -------------------------------------------------------------------------------- /cli/lint.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | function lint() { 5 | const tsLinter = require('./utils/ts-linter'); 6 | const result = tsLinter.lintSync(); 7 | 8 | process.exit(result.exitCode); 9 | } 10 | 11 | module.exports = lint; 12 | -------------------------------------------------------------------------------- /cli/serve.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const portfinder = require('portfinder'); 5 | const logger = require('@blackbaud/skyux-logger'); 6 | const assetsProcessor = require('../lib/assets-processor'); 7 | const localeAssetsProcessor = require('../lib/locale-assets-processor'); 8 | 9 | /** 10 | * Let users configure port via skyuxconfig.json first. 11 | * Else another plugin has specified devServer.port, use it. 12 | * Else, find us an available port. 13 | * @name getPort 14 | * @returns {Number} port 15 | */ 16 | function getPort(config, skyPagesConfig) { 17 | return new Promise((resolve, reject) => { 18 | const configPort = skyPagesConfig && 19 | skyPagesConfig.skyux && 20 | skyPagesConfig.skyux.app && 21 | skyPagesConfig.skyux.app.port; 22 | 23 | if (configPort) { 24 | resolve(configPort); 25 | } else if (config.devServer && config.devServer.port) { 26 | resolve(config.devServer.port); 27 | } else { 28 | portfinder.getPortPromise() 29 | .then(port => resolve(port)) 30 | .catch(err => reject(err)); 31 | } 32 | }); 33 | } 34 | 35 | /** 36 | * Executes the serve command. 37 | * @name serve 38 | * @name {Object} argv 39 | * @name {SkyPagesConfig} skyPagesConfig 40 | * @name {Webpack} webpack 41 | * @name {WebpackDevServer} WebpackDevServer 42 | * @returns null 43 | */ 44 | function serve(argv, skyPagesConfig, webpack, WebpackDevServer) { 45 | 46 | const webpackConfig = require('../config/webpack/serve.webpack.config'); 47 | let config = webpackConfig.getWebpackConfig(argv, skyPagesConfig); 48 | 49 | getPort(config, skyPagesConfig).then(port => { 50 | const localUrl = `https://localhost:${port}`; 51 | 52 | assetsProcessor.setSkyAssetsLoaderUrl(config, skyPagesConfig, localUrl); 53 | localeAssetsProcessor.prepareLocaleFiles(); 54 | 55 | // Save our found or defined port 56 | config.devServer.port = port; 57 | 58 | /* istanbul ignore else */ 59 | if (config.devServer.inline) { 60 | const inline = `webpack-dev-server/client?${localUrl}`; 61 | Object.keys(config.entry).forEach((entry) => { 62 | config.entry[entry].unshift(inline); 63 | }); 64 | } 65 | 66 | if (config.devServer.hot) { 67 | const hot = `webpack/hot/only-dev-server`; 68 | Object.keys(config.entry).forEach((entry) => { 69 | config.entry[entry].unshift(hot); 70 | }); 71 | 72 | // This is required in order to not have HMR requests routed to host. 73 | config.output.publicPath = `${localUrl}${config.devServer.publicPath}`; 74 | logger.info('Using hot module replacement.'); 75 | } 76 | 77 | const compiler = webpack(config); 78 | const server = new WebpackDevServer(compiler, config.devServer); 79 | server.listen(config.devServer.port, 'localhost', (err) => { 80 | if (err) { 81 | logger.error(err); 82 | } 83 | }); 84 | }).catch(err => logger.error(err)); 85 | 86 | } 87 | 88 | module.exports = serve; 89 | -------------------------------------------------------------------------------- /cli/test.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | /** 5 | * Spawns the karma test command. 6 | * @name test 7 | */ 8 | function test(command, argv) { 9 | const logger = require('@blackbaud/skyux-logger'); 10 | const Server = require('karma').Server; 11 | const path = require('path'); 12 | const glob = require('glob'); 13 | const tsLinter = require('./utils/ts-linter'); 14 | const configResolver = require('./utils/config-resolver'); 15 | const localeAssetsProcessor = require('../lib/locale-assets-processor'); 16 | 17 | argv = argv || process.argv; 18 | argv.command = command; 19 | 20 | const karmaConfigUtil = require('karma').config; 21 | const karmaConfigPath = configResolver.resolve(command, argv); 22 | const karmaConfig = karmaConfigUtil.parseConfig(karmaConfigPath); 23 | const specsPath = path.resolve(process.cwd(), 'src/app/**/*.spec.ts'); 24 | const specsGlob = glob.sync(specsPath); 25 | 26 | let lintResult; 27 | 28 | const onRunStart = () => { 29 | localeAssetsProcessor.prepareLocaleFiles(); 30 | lintResult = tsLinter.lintSync(); 31 | }; 32 | 33 | const onRunComplete = () => { 34 | if (lintResult && lintResult.exitCode > 0) { 35 | // Pull the logger out of the execution stream to let it print 36 | // after karma's coverage reporter. 37 | setTimeout(() => { 38 | logger.error('Process failed due to linting errors:'); 39 | lintResult.errors.forEach(error => logger.error(error)); 40 | }, 10); 41 | } 42 | }; 43 | 44 | const onExit = (exitCode) => { 45 | if (exitCode === 0) { 46 | if (lintResult) { 47 | exitCode = lintResult.exitCode; 48 | } 49 | } 50 | 51 | logger.info(`Karma has exited with ${exitCode}.`); 52 | process.exit(exitCode); 53 | }; 54 | 55 | const onBrowserError = () => { 56 | const stopper = require('karma').stopper; 57 | stopper.stop({}, () => onExit(1)); 58 | }; 59 | 60 | if (specsGlob.length === 0) { 61 | logger.info('No spec files located. Skipping test command.'); 62 | return onExit(0); 63 | } 64 | 65 | const server = new Server(karmaConfig, onExit); 66 | server.on('run_start', onRunStart); 67 | server.on('run_complete', onRunComplete); 68 | server.on('browser_error', onBrowserError); 69 | server.start(); 70 | } 71 | 72 | module.exports = test; 73 | -------------------------------------------------------------------------------- /cli/utils/browser.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const util = require('util'); 5 | const open = require('open'); 6 | const logger = require('@blackbaud/skyux-logger'); 7 | const hostUtils = require('../../utils/host-utils'); 8 | const skyPagesConfigUtil = require('../../config/sky-pages/sky-pages.config'); 9 | 10 | /** 11 | * Returns the querystring base for parameters allowed to be passed through. 12 | * PLEASE NOTE: The method is nearly duplicated in `runtime/params.ts`. 13 | * @name getQueryStringFromArgv 14 | * @param {Object} argv 15 | * @param {SkyPagesConfig} skyPagesConfig 16 | * @returns {string} 17 | */ 18 | function getQueryStringFromArgv(argv, skyPagesConfig) { 19 | 20 | const configParams = skyPagesConfig.skyux.params; 21 | 22 | let params; 23 | 24 | if (Array.isArray(configParams)) { 25 | params = configParams; 26 | } else { 27 | // Get the params that have truthy values, since false/undefined indicates 28 | // the parameter should not be added. 29 | params = Object.keys(configParams) 30 | .filter(configParam => configParams[configParam]); 31 | } 32 | 33 | let found = []; 34 | params.forEach(param => { 35 | if (argv[param]) { 36 | found.push(`${param}=${encodeURIComponent(argv[param])}`); 37 | } 38 | }); 39 | 40 | if (found.length) { 41 | return `?${found.join('&')}`; 42 | } 43 | 44 | return ''; 45 | } 46 | 47 | function browser(argv, skyPagesConfig, stats, port) { 48 | 49 | const queryStringBase = getQueryStringFromArgv(argv, skyPagesConfig); 50 | let localUrl = util.format( 51 | 'https://localhost:%s%s', 52 | port, 53 | skyPagesConfigUtil.getAppBase(skyPagesConfig) 54 | ); 55 | 56 | let hostUrl = hostUtils.resolve( 57 | queryStringBase, 58 | localUrl, 59 | stats.toJson().chunks, 60 | skyPagesConfig 61 | ); 62 | 63 | // Edge uses a different technique (protocol vs executable) 64 | if (argv.browser === 'edge') { 65 | const edge = 'microsoft-edge:'; 66 | argv.browser = undefined; 67 | hostUrl = edge + hostUrl; 68 | localUrl = edge + localUrl; 69 | } 70 | 71 | // Browser defaults to launching host 72 | argv.launch = argv.launch || 'host'; 73 | 74 | switch (argv.launch) { 75 | case 'local': 76 | 77 | // Only adding queryStringBase to the message + local url opened, 78 | // Meaning doesn't need those to communicate back to localhost 79 | localUrl += queryStringBase; 80 | 81 | logger.info(`Launching Local URL: ${localUrl}`); 82 | open(localUrl, argv.browser); 83 | break; 84 | 85 | case 'host': 86 | logger.info(`Launching Host URL: ${hostUrl}`); 87 | open(hostUrl, argv.browser); 88 | break; 89 | 90 | default: 91 | logger.info(`Host URL: ${hostUrl}`); 92 | logger.info(`Local URL: ${localUrl}`); 93 | break; 94 | } 95 | } 96 | 97 | module.exports = browser; 98 | -------------------------------------------------------------------------------- /cli/utils/config-resolver.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | const glob = require('glob'); 6 | const fs = require('fs-extra'); 7 | const logger = require('@blackbaud/skyux-logger'); 8 | 9 | const skyPagesConfigUtil = require('../../config/sky-pages/sky-pages.config'); 10 | 11 | function getPath(command, platform, root, dir) { 12 | let filename; 13 | switch (command) { 14 | case 'e2e': 15 | case 'visual': 16 | filename = `config/protractor/protractor.conf.js`; 17 | break; 18 | 19 | // Defaulting to karma so dev-runtime and src-app can be passed in via our test suite. 20 | default: 21 | filename = `config/karma/${command}.karma.conf.js`; 22 | break; 23 | } 24 | 25 | return path.join(root, dir, platform, filename); 26 | } 27 | 28 | function resolve(command, argv) { 29 | 30 | const platform = argv.platform || ''; 31 | const internal = getPath( 32 | command, 33 | platform, 34 | skyPagesConfigUtil.outPath(), 35 | '' 36 | ); 37 | 38 | // Using glob so we can find skyux-builder-config regardless of npm install location 39 | let external = glob.sync(getPath( 40 | command, 41 | platform, 42 | process.cwd(), 43 | 'node_modules/**/skyux-builder-config/' 44 | )); 45 | 46 | let config; 47 | if (external.length > 1) { 48 | logger.warn(`Found multiple external config files.`); 49 | external = external.slice(0, 1); 50 | } 51 | 52 | if (external.length === 1) { 53 | logger.info(`Using external config ${external[0]}`); 54 | config = external[0]; 55 | } else if (fs.existsSync(internal)) { 56 | logger.info(`Using internal config ${internal}`); 57 | config = internal; 58 | } else { 59 | logger.error('Error locating a config file.'); 60 | process.exit(1); 61 | } 62 | 63 | return config; 64 | } 65 | 66 | module.exports = { 67 | resolve 68 | }; 69 | -------------------------------------------------------------------------------- /cli/utils/prepare-library-package.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const logger = require('@blackbaud/skyux-logger'); 6 | const skyPagesConfigUtil = require('../../config/sky-pages/sky-pages.config'); 7 | 8 | function makePackageFileForDist() { 9 | const contents = fs.readJsonSync( 10 | skyPagesConfigUtil.spaPath('package.json') 11 | ); 12 | contents.module = 'index.js'; 13 | contents.main = 'bundles/bundle.umd.js'; 14 | 15 | fs.writeJsonSync( 16 | skyPagesConfigUtil.spaPath('dist', 'package.json'), 17 | contents, 18 | { spaces: 2 } 19 | ); 20 | } 21 | 22 | function copyFilesToDist() { 23 | const pathsToCopy = [ 24 | ['README.md'], 25 | ['CHANGELOG.md'], 26 | ['src', 'assets'] 27 | ]; 28 | 29 | pathsToCopy.forEach(pathArr => { 30 | const sourcePath = skyPagesConfigUtil.spaPath(...pathArr); 31 | if (fs.existsSync(sourcePath)) { 32 | fs.copySync( 33 | sourcePath, 34 | skyPagesConfigUtil.spaPath('dist', ...pathArr) 35 | ); 36 | } else { 37 | logger.warn(`File(s) not found: ${sourcePath}`); 38 | } 39 | }); 40 | } 41 | 42 | module.exports = () => { 43 | makePackageFileForDist(); 44 | copyFilesToDist(); 45 | }; 46 | -------------------------------------------------------------------------------- /cli/utils/run-compiler.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const logger = require('@blackbaud/skyux-logger'); 5 | 6 | const runCompiler = (webpack, config) => { 7 | const compiler = webpack(config); 8 | 9 | return new Promise((resolve, reject) => { 10 | compiler.run((err, stats) => { 11 | if (err) { 12 | return reject(err); 13 | } 14 | 15 | const jsonStats = stats.toJson(); 16 | 17 | if (jsonStats.errors.length) { 18 | return reject(jsonStats.errors); 19 | } 20 | 21 | if (jsonStats.warnings.length) { 22 | logger.warn(jsonStats.warnings); 23 | } 24 | 25 | // Normal logging is handled by SimpleProgressWebpackPlugin in common.webpack.config.js 26 | resolve(stats); 27 | }); 28 | }); 29 | }; 30 | 31 | module.exports = runCompiler; 32 | -------------------------------------------------------------------------------- /cli/utils/server.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const portfinder = require('portfinder'); 7 | const express = require('express'); 8 | const https = require('https'); 9 | const cors = require('cors'); 10 | const logger = require('@blackbaud/skyux-logger'); 11 | 12 | const app = express(); 13 | 14 | let server; 15 | 16 | /** 17 | * Starts the httpServer 18 | * @name start 19 | */ 20 | function start(root, distPath) { 21 | return new Promise((resolve, reject) => { 22 | 23 | const dist = path.resolve(process.cwd(), distPath || 'dist'); 24 | 25 | logger.info('Creating web server'); 26 | app.use(cors()); 27 | 28 | logger.info(`Exposing static directory: ${dist}`); 29 | app.use(express.static(dist)); 30 | if (root) { 31 | logger.info(`Mapping server requests from ${root} to ${dist}`); 32 | app.use(root, express.static(dist)); 33 | } 34 | 35 | const options = { 36 | cert: fs.readFileSync(path.resolve(__dirname, '../../ssl/server.crt')), 37 | key: fs.readFileSync(path.resolve(__dirname, '../../ssl/server.key')) 38 | }; 39 | 40 | server = https.createServer(options, app); 41 | server.on('error', reject); 42 | 43 | logger.info('Requesting open port...'); 44 | portfinder 45 | .getPortPromise() 46 | .then(port => { 47 | logger.info(`Open port found: ${port}`); 48 | logger.info('Starting web server...'); 49 | server.listen(port, 'localhost', () => { 50 | logger.info('Web server running.'); 51 | resolve(port); 52 | }); 53 | }) 54 | .catch(reject); 55 | }); 56 | } 57 | 58 | /** 59 | * Kills the server if it exists 60 | * @name kill 61 | */ 62 | function stop() { 63 | if (server) { 64 | logger.info('Stopping http server'); 65 | server.close(); 66 | server = null; 67 | } 68 | } 69 | 70 | module.exports = { 71 | start: start, 72 | stop: stop 73 | }; 74 | -------------------------------------------------------------------------------- /cli/utils/stage-library-ts.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const glob = require('glob'); 6 | const path = require('path'); 7 | const sass = require('node-sass'); 8 | const tildeImporter = require('node-sass-tilde-importer'); 9 | 10 | const skyPagesConfigUtil = require('../../config/sky-pages/sky-pages.config'); 11 | const spaPathTempSrc = skyPagesConfigUtil.spaPathTemp(); 12 | 13 | function copySource() { 14 | fs.copySync( 15 | skyPagesConfigUtil.spaPath('src', 'app', 'public'), 16 | skyPagesConfigUtil.spaPathTemp() 17 | ); 18 | } 19 | 20 | function deleteNonDistFiles() { 21 | let files = glob.sync(`${spaPathTempSrc}/**/*.spec.ts`); 22 | files.forEach(file => fs.removeSync(file)); 23 | } 24 | 25 | function inlineHtmlCss() { 26 | const templateUrlRegEx = /templateUrl\:\s*'(.+?\.html)'/gi; 27 | const styleUrlsRegEx = /styleUrls\:\s*\[\s*'(.+?\.scss)'\s*]/gi; 28 | 29 | let files = glob.sync(`${spaPathTempSrc}/**/*.ts`); 30 | 31 | files.forEach((file) => { 32 | let fileContents = fs.readFileSync(file, { encoding: 'utf8' }); 33 | let dirname = path.dirname(file); 34 | let matches; 35 | 36 | // templateUrl 37 | matches = templateUrlRegEx.exec(fileContents); 38 | while (matches) { 39 | let requireFile = path.join(dirname, matches[1]); 40 | let requireContents = getFileContents(requireFile); 41 | requireContents = `template: ${requireContents}`; 42 | fileContents = fileContents.replace(matches[0], requireContents); 43 | matches = templateUrlRegEx.exec(fileContents); 44 | 45 | // Since we're changing the file contents in each iteration and since the regex is stateful 46 | // we need to reset the regex; otherwise it might not be able to locate subsequent matches 47 | // after the first replacement. 48 | templateUrlRegEx.lastIndex = 0; 49 | } 50 | 51 | // styleUrls 52 | matches = styleUrlsRegEx.exec(fileContents); 53 | while (matches) { 54 | let requireFile = path.join(dirname, matches[1]); 55 | let requireContents = getFileContents(requireFile); 56 | requireContents = `styles: [${requireContents}]`; 57 | fileContents = fileContents.replace(matches[0], requireContents); 58 | styleUrlsRegEx.lastIndex = 0; 59 | matches = styleUrlsRegEx.exec(fileContents); 60 | } 61 | 62 | fs.writeFileSync(file, fileContents, { encoding: 'utf8' }); 63 | }); 64 | } 65 | 66 | function getFileContents(filePath) { 67 | let contents = ''; 68 | switch (path.extname(filePath)) { 69 | case '.scss': 70 | contents = compileSass(filePath); 71 | break; 72 | 73 | case '.html': 74 | contents = getHtmlContents(filePath); 75 | break; 76 | } 77 | 78 | contents = contents 79 | .toString() 80 | .replace(/\\f/g, '\\\\f') 81 | .replace(/`/g, '\\`'); 82 | 83 | return '`' + contents + '`'; 84 | } 85 | 86 | function getHtmlContents(filePath) { 87 | return fs.readFileSync(filePath).toString(); 88 | } 89 | 90 | function compileSass(filePath) { 91 | return sass.renderSync({ 92 | file: filePath, 93 | importer: tildeImporter, 94 | outputStyle: 'compressed' 95 | }).css; 96 | } 97 | 98 | module.exports = () => { 99 | copySource(); 100 | deleteNonDistFiles(); 101 | inlineHtmlCss(); 102 | }; 103 | -------------------------------------------------------------------------------- /cli/utils/ts-linter.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const spawn = require('cross-spawn'); 5 | const logger = require('@blackbaud/skyux-logger'); 6 | const skyPagesConfigUtil = require('../../config/sky-pages/sky-pages.config'); 7 | 8 | const flags = [ 9 | '--type-check', 10 | '--project', 11 | skyPagesConfigUtil.spaPath('tsconfig.json'), 12 | '--config', 13 | skyPagesConfigUtil.spaPath('tslint.json'), 14 | '--exclude', 15 | '**/node_modules/**/*.ts' 16 | ]; 17 | 18 | function lintSync() { 19 | logger.info('Starting TSLint...'); 20 | 21 | const spawnResult = spawn.sync('./node_modules/.bin/tslint', flags); 22 | 23 | // Convert buffers to strings. 24 | let output = []; 25 | spawnResult.output.forEach((buffer) => { 26 | if (buffer === null) { 27 | return; 28 | } 29 | 30 | const str = buffer.toString().trim(); 31 | if (str) { 32 | output.push(str); 33 | } 34 | }); 35 | 36 | // Convert multi-line errors into single errors. 37 | let errors = []; 38 | output.forEach((str) => { 39 | errors = errors.concat(str.split(/\r?\n/)); 40 | }); 41 | 42 | // Print linting results to console. 43 | errors.forEach(error => logger.error(error)); 44 | const plural = (errors.length === 1) ? '' : 's'; 45 | logger.info(`TSLint finished with ${errors.length} error${plural}.`); 46 | 47 | return { 48 | exitCode: spawnResult.status, 49 | errors: errors 50 | }; 51 | } 52 | 53 | module.exports = { 54 | lintSync 55 | }; 56 | -------------------------------------------------------------------------------- /cli/version.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | const logger = require('@blackbaud/skyux-logger'); 6 | 7 | /** 8 | * Returns the version from package.json. 9 | * @name version 10 | */ 11 | function version() { 12 | const packageJson = require(path.resolve(__dirname, '..', 'package.json')); 13 | logger.info('@blackbaud/skyux-builder: %s', packageJson.version); 14 | } 15 | 16 | module.exports = version; 17 | -------------------------------------------------------------------------------- /config/axe/axe.config.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | // Defaults derived from: https://github.com/dequelabs/axe-core 5 | const defaults = { 6 | rules: { 7 | 'area-alt': { 'enabled': true }, 8 | 'audio-caption': { 'enabled': true }, 9 | 'button-name': { 'enabled': true }, 10 | 'document-title': { 'enabled': true }, 11 | 'empty-heading': { 'enabled': true }, 12 | 'frame-title': { 'enabled': true }, 13 | 'frame-title-unique': { 'enabled': true }, 14 | 'image-alt': { 'enabled': true }, 15 | 'image-redundant-alt': { 'enabled': true }, 16 | 'input-image-alt': { 'enabled': true }, 17 | 'link-name': { 'enabled': true }, 18 | 'object-alt': { 'enabled': true }, 19 | 'server-side-image-map': { 'enabled': true }, 20 | 'video-caption': { 'enabled': true }, 21 | 'video-description': { 'enabled': true }, 22 | 23 | 'definition-list': { 'enabled': true }, 24 | 'dlitem': { 'enabled': true }, 25 | 'heading-order': { 'enabled': true }, 26 | 'href-no-hash': { 'enabled': true }, 27 | 'layout-table': { 'enabled': true }, 28 | 'list': { 'enabled': true }, 29 | 'listitem': { 'enabled': true }, 30 | 'p-as-heading': { 'enabled': true }, 31 | 32 | 'scope-attr-valid': { 'enabled': true }, 33 | 'table-duplicate-name': { 'enabled': true }, 34 | 'table-fake-caption': { 'enabled': true }, 35 | 'td-has-header': { 'enabled': true }, 36 | 'td-headers-attr': { 'enabled': true }, 37 | 'th-has-data-cells': { 'enabled': true }, 38 | 39 | 'duplicate-id': { 'enabled': true }, 40 | 'html-has-lang': { 'enabled': true }, 41 | 'html-lang-valid': { 'enabled': true }, 42 | 'meta-refresh': { 'enabled': true }, 43 | 'valid-lang': { 'enabled': true }, 44 | 45 | 'checkboxgroup': { 'enabled': true }, 46 | 'label': { 'enabled': true }, 47 | 'radiogroup': { 'enabled': true }, 48 | 49 | 'accesskeys': { 'enabled': true }, 50 | 'bypass': { 'enabled': true }, 51 | 'tabindex': { 'enabled': true }, 52 | 53 | // TODO: this should be re-enabled when we upgrade to axe-core ^3.1.1 (https://github.com/dequelabs/axe-core/issues/961) 54 | 'aria-allowed-attr': { 'enabled': false }, 55 | 'aria-required-attr': { 'enabled': true }, 56 | 'aria-required-children': { 'enabled': true }, 57 | 'aria-required-parent': { 'enabled': true }, 58 | 'aria-roles': { 'enabled': true }, 59 | 'aria-valid-attr': { 'enabled': true }, 60 | 'aria-valid-attr-value': { 'enabled': true }, 61 | 62 | 'blink': { 'enabled': true }, 63 | 'color-contrast': { 'enabled': true }, 64 | 'link-in-text-block': { 'enabled': true }, 65 | 'marquee': { 'enabled': true }, 66 | 'meta-viewport': { 'enabled': true }, 67 | 'meta-viewport-large': { 'enabled': true } 68 | } 69 | }; 70 | 71 | module.exports = { 72 | getConfig: () => { 73 | const skyPagesConfigUtil = require('../sky-pages/sky-pages.config'); 74 | const skyPagesConfig = skyPagesConfigUtil.getSkyPagesConfig(); 75 | 76 | let config = {}; 77 | 78 | // Merge rules from skyux config. 79 | if (skyPagesConfig.skyux.a11y && skyPagesConfig.skyux.a11y.rules) { 80 | config.rules = Object.assign({}, defaults.rules, skyPagesConfig.skyux.a11y.rules); 81 | } 82 | 83 | // The consuming SPA wishes to disable all rules. 84 | if (skyPagesConfig.skyux.a11y === false) { 85 | config.rules = Object.assign({}, defaults.rules); 86 | Object.keys(config.rules).forEach((key) => { 87 | config.rules[key].enabled = false; 88 | }); 89 | } 90 | 91 | if (!config.rules) { 92 | return defaults; 93 | } 94 | 95 | return config; 96 | } 97 | }; 98 | -------------------------------------------------------------------------------- /config/karma/dev-runtime.karma.conf.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | function addRuntimeAlias(webpackConfig, runtimePath, path) { 5 | webpackConfig.resolve.alias['@blackbaud/skyux-builder/runtime' + path] = runtimePath + path; 6 | } 7 | 8 | /** 9 | * Requires the shared karma config and sets any local properties. 10 | * @name getConfig 11 | * @param {Object} config 12 | */ 13 | function getConfig(config) { 14 | 15 | const path = require('path'); 16 | 17 | const DefinePlugin = require('webpack/lib/DefinePlugin'); 18 | const testWebpackConfig = require('../webpack/test.webpack.config'); 19 | const skyPagesConfigUtil = require('../sky-pages/sky-pages.config'); 20 | const testKarmaConf = require('./test.karma.conf'); 21 | 22 | const runtimePath = path.resolve(process.cwd(), 'runtime'); 23 | const skyPagesConfig = skyPagesConfigUtil.getSkyPagesConfig('test'); 24 | let webpackConfig = testWebpackConfig.getWebpackConfig(skyPagesConfig); 25 | 26 | // Import shared karma config 27 | testKarmaConf(config); 28 | 29 | // First DefinePlugin wins so we want to override the normal src/app/ value in ROOT_DIR 30 | webpackConfig.plugins.unshift( 31 | new DefinePlugin({ 32 | 'ROOT_DIR': JSON.stringify(runtimePath) 33 | }) 34 | ); 35 | 36 | // Adjust the loader src path. 37 | webpackConfig.module.rules[webpackConfig.module.rules.length - 1].include = runtimePath; 38 | 39 | // This is needed exclusively for internal runtime unit tests, 40 | // which is why it's here instead of alias-builder or the shared test.webpack.config.js 41 | addRuntimeAlias(webpackConfig, runtimePath, ''); 42 | addRuntimeAlias(webpackConfig, runtimePath, '/i18n'); 43 | 44 | // Remove sky-style-loader 45 | delete config.preprocessors['../../utils/spec-styles.js']; 46 | config.files.pop(); 47 | 48 | config.set({ 49 | webpack: webpackConfig, 50 | coverageReporter: { 51 | dir: path.join(process.cwd(), 'coverage', 'runtime') 52 | } 53 | }); 54 | } 55 | 56 | module.exports = getConfig; 57 | -------------------------------------------------------------------------------- /config/karma/dev-src-app.karma.conf.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | /** 5 | * Requires the shared karma config and sets any local properties. 6 | * @name getConfig 7 | * @param {Object} config 8 | */ 9 | function getConfig(config) { 10 | 11 | const path = require('path'); 12 | 13 | const DefinePlugin = require('webpack/lib/DefinePlugin'); 14 | const testWebpackConfig = require('../webpack/test.webpack.config'); 15 | const skyPagesConfigUtil = require('../sky-pages/sky-pages.config'); 16 | const testKarmaConf = require('./test.karma.conf'); 17 | 18 | const runtimePath = path.resolve(process.cwd(), 'runtime'); 19 | const srcPath = path.resolve(process.cwd(), 'src', 'app'); 20 | const skyPagesConfig = skyPagesConfigUtil.getSkyPagesConfig('test'); 21 | let webpackConfig = testWebpackConfig.getWebpackConfig(skyPagesConfig); 22 | 23 | // Import shared karma config 24 | testKarmaConf(config); 25 | 26 | // First DefinePlugin wins so we want to override the normal src/app/ value in ROOT_DIR 27 | webpackConfig.plugins.unshift( 28 | new DefinePlugin({ 29 | 'ROOT_DIR': JSON.stringify(srcPath) 30 | }) 31 | ); 32 | 33 | // Adjust the loader src path. 34 | webpackConfig.module.rules[webpackConfig.module.rules.length - 1].include = srcPath; 35 | 36 | // This is needed exclusively for internal runtime unit tests, 37 | // which is why it's here instead of alias-builder or the shared test.webpack.config.js 38 | // It's relative from src/app/ 39 | webpackConfig.resolve.alias['@blackbaud/skyux-builder/runtime'] = runtimePath; 40 | 41 | // Instead of adding skyux2 as a dependency of skyux-builder 42 | webpackConfig.resolve.alias['@skyux/theme/css/sky.css'] = 43 | '../../utils/runtime-test-skyux.css'; 44 | 45 | // Remove sky-style-loader 46 | delete config.preprocessors['../../utils/spec-styles.js']; 47 | config.files.pop(); 48 | 49 | config.set({ 50 | webpack: webpackConfig, 51 | coverageReporter: { 52 | dir: path.join(process.cwd(), 'coverage', 'src-app') 53 | } 54 | }); 55 | } 56 | 57 | module.exports = getConfig; 58 | -------------------------------------------------------------------------------- /config/karma/pact.karma.conf.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | /** 5 | * Requires the shared karma config and sets any local properties. 6 | * @name getConfig 7 | * @param {Object} config 8 | */ 9 | function getConfig(config) { 10 | const logger = require('@blackbaud/skyux-logger'); 11 | const minimist = require('minimist'); 12 | const argv = minimist(process.argv.slice(2)); 13 | require(`./${argv.watch ? 'watch' : 'test'}.karma.conf`)(config); 14 | let skyPagesConfig = require('../sky-pages/sky-pages.config').getSkyPagesConfig(argv._[0]); 15 | let testWebpackConfig = require('../webpack/test.webpack.config'); 16 | const path = require('path'); 17 | const pactServers = require('../../utils/pact-servers'); 18 | 19 | skyPagesConfig.runtime.pactConfig = {}; 20 | skyPagesConfig.runtime.pactConfig.providers = pactServers.getAllPactServers(); 21 | skyPagesConfig.runtime.pactConfig.pactProxyServer = pactServers.getPactProxyServer(); 22 | 23 | if (skyPagesConfig.skyux.pacts) { 24 | var i = 0; 25 | skyPagesConfig.skyux.pacts.forEach((pact) => { 26 | // set pact settings not specified in config file 27 | pact.log = pact.log || path.resolve(process.cwd(), 'logs', `pact-${pact.provider}.log`); 28 | pact.dir = pact.dir || path.resolve(process.cwd(), 'pacts'); 29 | pact.host = pactServers.getPactServer(pact.provider).host; 30 | pact.port = pactServers.getPactServer(pact.provider).port; 31 | pact.pactFileWriteMode = pact.pactFileWriteMode || 'overwrite'; 32 | 33 | i++; 34 | }); 35 | } else { 36 | logger.error('No pact entry in configuration!'); 37 | } 38 | 39 | config.set({ 40 | frameworks: config.frameworks.concat('pact'), 41 | files: config.files.concat(path.resolve(process.cwd(), 'node_modules/@pact-foundation/pact-web', 42 | `pact-web.js`)), 43 | pact: skyPagesConfig.skyux.pacts, 44 | plugins: config.plugins.concat('@pact-foundation/karma-pact'), 45 | webpack: testWebpackConfig.getWebpackConfig(skyPagesConfig, argv) 46 | 47 | }); 48 | 49 | } 50 | 51 | module.exports = getConfig; 52 | -------------------------------------------------------------------------------- /config/karma/test.karma.conf.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | /** 5 | * Requires the shared karma config and sets any local properties. 6 | * @name getConfig 7 | * @param {Object} config 8 | */ 9 | function getConfig(config) { 10 | require('./shared.karma.conf')(config); 11 | let configuration = { 12 | browsers: [ 13 | 'Chrome' 14 | ], 15 | customLaunchers: { 16 | Chrome_travis_ci: { 17 | base: 'Chrome', 18 | flags: ['--no-sandbox'] 19 | } 20 | } 21 | }; 22 | 23 | if (process.env.TRAVIS) { 24 | configuration.browsers = ['Chrome_travis_ci']; 25 | } 26 | 27 | config.set(configuration); 28 | } 29 | 30 | module.exports = getConfig; 31 | -------------------------------------------------------------------------------- /config/karma/watch.karma.conf.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | /** 5 | * Requires the shared karma config and sets any local properties. 6 | * @name getConfig 7 | * @param {Object} config 8 | */ 9 | function getConfig(config) { 10 | require('./test.karma.conf')(config); 11 | config.set({ 12 | autoWatch: true, 13 | singleRun: false 14 | }); 15 | } 16 | 17 | module.exports = getConfig; 18 | -------------------------------------------------------------------------------- /config/protractor/protractor-dev.conf.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const path = require('path'); 6 | const merge = require('../../utils/merge'); 7 | const SpecReporter = require('jasmine-spec-reporter').SpecReporter; 8 | 9 | const common = require('../../e2e/shared/common'); 10 | const commonConfig = require('./protractor.conf'); 11 | 12 | let config = { 13 | specs: [ 14 | path.join(process.cwd(), 'e2e', '**', '*.e2e-spec.js') 15 | ], 16 | jasmineNodeOpts: { 17 | defaultTimeoutInterval: 480000 // git clone, npm install, and skyux build can be slow 18 | }, 19 | onPrepare: () => { 20 | jasmine.getEnv().addReporter(new SpecReporter()); 21 | 22 | return new Promise((resolve, reject) => { 23 | 24 | if (fs.existsSync(common.tmp) && !process.argv.includes('--clean')) { 25 | 26 | console.log(''); 27 | console.log('*********'); 28 | console.log('Running fast e2e tests'); 29 | console.log(`Delete ${common.tmp} to have the install steps run.`); 30 | console.log('*********'); 31 | console.log(''); 32 | 33 | resolve(); 34 | 35 | } else { 36 | 37 | const url = 'https://github.com/blackbaud/skyux-template'; 38 | const branch = 'builder-dev'; 39 | 40 | console.log('Running command using full install.'); 41 | common.rimrafPromise(common.tmp) 42 | .then(() => common.exec(`git`, [ 43 | `clone`, 44 | `-b`, 45 | branch, 46 | `--single-branch`, 47 | url, 48 | common.tmp 49 | ])) 50 | .then(() => { 51 | 52 | // This method attempts to take what would be installed from builder in to the SPA. 53 | // It was the only reliable way I could convince NPM to install everything needed. 54 | const spaPkgPath = path.resolve(common.tmp, 'package.json'); 55 | const spaPkgJson = fs.readJsonSync(spaPkgPath); 56 | 57 | const builderPkgPath = path.resolve('package.json'); 58 | const builderPkgJson = fs.readJsonSync(builderPkgPath); 59 | 60 | Object.keys(builderPkgJson.dependencies).forEach(dep => { 61 | spaPkgJson.dependencies[dep] = builderPkgJson.dependencies[dep]; 62 | }); 63 | 64 | // Remove any installed versions of Builder. 65 | delete spaPkgJson.devDependencies['@blackbaud/skyux-builder']; 66 | 67 | fs.writeJsonSync(spaPkgPath, spaPkgJson, { spaces: 2 }); 68 | }) 69 | .then(() => common.exec(`npm`, [`i`], common.cwdOpts)) 70 | .then(() => { 71 | // Copy builder's local source to node_modules. 72 | const files = [ 73 | 'cli', 74 | 'config', 75 | 'e2e', 76 | 'lib', 77 | 'loader', 78 | 'plugin', 79 | 'runtime', 80 | 'src', 81 | 'ssl', 82 | 'utils', 83 | 'index.js', 84 | 'package.json', 85 | 'skyuxconfig.json', 86 | 'tsconfig.json', 87 | 'tslint.json' 88 | ]; 89 | 90 | files.forEach(file => { 91 | fs.copySync( 92 | file, 93 | path.resolve( 94 | common.tmp, 95 | `node_modules/@blackbaud/skyux-builder/${file}` 96 | ) 97 | ); 98 | }); 99 | }) 100 | .then(resolve) 101 | .catch(reject); 102 | 103 | } 104 | }); 105 | }, 106 | 107 | // Catch any rogue servers 108 | onComplete: () => common.afterAll 109 | }; 110 | 111 | // In CI, use firefox 112 | if (process.env.TRAVIS) { 113 | config.capabilities = { 114 | browserName: 'chrome', 115 | 'chromeOptions': { 116 | 'args': ['--disable-extensions --ignore-certificate-errors'] 117 | } 118 | }; 119 | } 120 | 121 | exports.config = merge(commonConfig.config, config); 122 | -------------------------------------------------------------------------------- /config/protractor/protractor.conf.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | const minimist = require('minimist'); 6 | const SpecReporter = require('jasmine-spec-reporter').SpecReporter; 7 | const logger = require('@blackbaud/skyux-logger'); 8 | 9 | // See minimist documentation regarding `argv._` https://github.com/substack/minimist 10 | const argv = minimist(process.argv.slice(2)); 11 | const skyPagesConfig = require('../sky-pages/sky-pages.config').getSkyPagesConfig(argv._[0]); 12 | 13 | exports.config = { 14 | skyPagesConfig: skyPagesConfig, 15 | allScriptsTimeout: 11000, 16 | specs: [ 17 | path.join( 18 | process.cwd(), 19 | 'e2e', 20 | '**', 21 | '*.e2e-spec.ts' 22 | ) 23 | ], 24 | capabilities: { 25 | 'browserName': 'chrome', 26 | 'chromeOptions': { 27 | 'args': ['--disable-extensions --ignore-certificate-errors'] 28 | } 29 | }, 30 | directConnect: true, 31 | // seleniumAddress: 'http://localhost:4444/wd/hub', 32 | framework: 'jasmine', 33 | jasmineNodeOpts: { 34 | showColors: logger.logColor, 35 | defaultTimeoutInterval: 30000 36 | }, 37 | useAllAngular2AppRoots: true, 38 | beforeLaunch: function () { 39 | require('ts-node').register({ ignore: false }); 40 | }, 41 | 42 | onPrepare: function () { 43 | jasmine.getEnv().addReporter(new SpecReporter()); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /config/webpack/alias-builder.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const skyPagesConfigUtil = require('../sky-pages/sky-pages.config'); 7 | 8 | function spaPath() { 9 | return skyPagesConfigUtil.spaPath.apply(skyPagesConfigUtil, arguments); 10 | } 11 | 12 | function outPath() { 13 | return skyPagesConfigUtil.outPath.apply(skyPagesConfigUtil, arguments); 14 | } 15 | 16 | /** 17 | * Sets an alias to the specified module using the SPA path if the file exists in the SPA; 18 | * otherwise it sets the alias to the file in SKY UX Builder. 19 | * @name setSpaAlias 20 | * @param {Object} alias 21 | * @param {String} moduleName 22 | * @param {String} path 23 | */ 24 | function setSpaAlias(alias, moduleName, path) { 25 | let resolvedPath = spaPath(path); 26 | 27 | if (!fs.existsSync(resolvedPath)) { 28 | resolvedPath = outPath(path); 29 | } 30 | 31 | alias['sky-pages-internal/' + moduleName] = resolvedPath; 32 | } 33 | 34 | module.exports = { 35 | buildAliasList: function (skyPagesConfig) { 36 | let alias = { 37 | 'sky-pages-spa/src': spaPath('src'), 38 | 'sky-pages-internal/runtime': outPath('runtime') 39 | }; 40 | 41 | // Order here is very important; the more specific CSS alias must go before 42 | // the more generic dist one. 43 | if (skyPagesConfig.skyux.cssPath) { 44 | alias['@skyux/theme/css/sky.css'] = spaPath(skyPagesConfig.skyux.cssPath); 45 | } 46 | 47 | if (skyPagesConfig.skyux.importPath) { 48 | alias['@blackbaud/skyux/dist'] = spaPath(skyPagesConfig.skyux.importPath); 49 | } 50 | 51 | // Allow SPAs to provide custom module aliases. 52 | const moduleAliases = skyPagesConfig.skyux.moduleAliases; 53 | if (moduleAliases) { 54 | const command = skyPagesConfig.runtime.command; 55 | 56 | Object.keys(moduleAliases).forEach((key) => { 57 | const modulePath = moduleAliases[key]; 58 | 59 | switch (command) { 60 | case 'build': 61 | case 'e2e': 62 | alias[key] = skyPagesConfigUtil.spaPathTemp(modulePath); 63 | break; 64 | default: 65 | alias[key] = spaPath(modulePath); 66 | break; 67 | } 68 | }); 69 | } 70 | 71 | setSpaAlias( 72 | alias, 73 | 'src/app/app-extras.module', 74 | path.join('src', 'app', 'app-extras.module.ts') 75 | ); 76 | 77 | setSpaAlias(alias, 'src/main', path.join('src', 'main.ts')); 78 | 79 | return alias; 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /config/webpack/build-aot.webpack.config.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const webpack = require('webpack'); 5 | const webpackMerge = require('webpack-merge'); 6 | const ngtools = require('@ngtools/webpack'); 7 | const skyPagesConfigUtil = require('../sky-pages/sky-pages.config'); 8 | const SaveMetadata = require('../../plugin/save-metadata'); 9 | 10 | /** 11 | * Returns the default webpackConfig. 12 | * @name getDefaultWebpackConfig 13 | * @returns {WebpackConfig} webpackConfig 14 | */ 15 | function getWebpackConfig(skyPagesConfig, argv) { 16 | const common = require('./common.webpack.config'); 17 | 18 | // Webpackmege will attempt to merge each entries array, so we need to delete it 19 | let commonConfig = common.getWebpackConfig(skyPagesConfig, argv); 20 | commonConfig.entry = null; 21 | 22 | // Since the preloader is executed against the file system during an AoT build, 23 | // we need to remove it from the webpack config, otherwise it will get executed twice. 24 | commonConfig.module.rules = commonConfig.module.rules 25 | .filter((rule) => { 26 | const isPreloader = /(\/|\\)sky-processor(\/|\\)/.test(rule.loader); 27 | return (!isPreloader); 28 | }); 29 | 30 | return webpackMerge(commonConfig, { 31 | entry: { 32 | polyfills: [skyPagesConfigUtil.spaPathTempSrc('polyfills.ts')], 33 | vendor: [skyPagesConfigUtil.spaPathTempSrc('vendor.ts')], 34 | skyux: [skyPagesConfigUtil.spaPathTempSrc('skyux.ts')], 35 | app: [skyPagesConfigUtil.spaPathTempSrc('main-internal.aot.ts')] 36 | }, 37 | 38 | // Disable sourcemaps for production: 39 | // https://webpack.js.org/configuration/devtool/#production 40 | devtool: undefined, 41 | 42 | module: { 43 | rules: [ 44 | { 45 | test: /\.ts$/, 46 | loader: '@ngtools/webpack' 47 | } 48 | ] 49 | }, 50 | plugins: [ 51 | new ngtools.AotPlugin({ 52 | tsConfigPath: skyPagesConfigUtil.spaPathTempSrc('tsconfig.json'), 53 | entryModule: skyPagesConfigUtil.spaPathTempSrc('app', 'app.module') + '#AppModule', 54 | // Type checking handled by Builder's ts-linter utility. 55 | typeChecking: false 56 | }), 57 | SaveMetadata, 58 | new webpack.optimize.UglifyJsPlugin({ 59 | beautify: false, 60 | comments: false, 61 | compress: { warnings: false }, 62 | mangle: { screw_ie8: true, keep_fnames: true } 63 | }) 64 | ] 65 | }); 66 | } 67 | 68 | module.exports = { 69 | getWebpackConfig: getWebpackConfig 70 | }; 71 | -------------------------------------------------------------------------------- /config/webpack/build-public-library.webpack.config.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const ngtools = require('@ngtools/webpack'); 5 | const fs = require('fs-extra'); 6 | const webpack = require('webpack'); 7 | const skyPagesConfigUtil = require('../sky-pages/sky-pages.config'); 8 | 9 | function parseRegExp(name) { 10 | const escaped = name 11 | .replace(/\./g, String.raw`\.`) 12 | .replace(/\//g, String.raw`\/`) 13 | .replace(/\-/g, String.raw`\-`); 14 | return new RegExp(`^${escaped}`); 15 | } 16 | 17 | function getWebpackConfig(skyPagesConfig) { 18 | const libraryName = skyPagesConfig.skyux.name || 'SkyAppLibrary'; 19 | 20 | const builderPackageJson = fs.readJsonSync( 21 | skyPagesConfigUtil.outPath('package.json') 22 | ); 23 | 24 | const spaPackageJson = fs.readJsonSync( 25 | skyPagesConfigUtil.spaPath('package.json') 26 | ); 27 | 28 | let dependencies = []; 29 | if (builderPackageJson.dependencies) { 30 | dependencies = Object.keys(builderPackageJson.dependencies); 31 | } 32 | 33 | if (builderPackageJson.peerDependencies) { 34 | dependencies = dependencies.concat(Object.keys(builderPackageJson.peerDependencies)); 35 | } 36 | 37 | if (spaPackageJson.dependencies) { 38 | dependencies = dependencies.concat(Object.keys(spaPackageJson.dependencies)); 39 | } 40 | 41 | if (spaPackageJson.peerDependencies) { 42 | dependencies = dependencies.concat(Object.keys(spaPackageJson.peerDependencies)); 43 | } 44 | 45 | // Remove duplicates from array. 46 | // https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates 47 | const externals = [...new Set(dependencies)] 48 | .map(key => parseRegExp(key)); 49 | 50 | return { 51 | entry: skyPagesConfigUtil.spaPathTemp('index.ts'), 52 | output: { 53 | path: skyPagesConfigUtil.spaPath('dist', 'bundles'), 54 | filename: 'bundle.umd.js', 55 | libraryTarget: 'umd', 56 | library: libraryName 57 | }, 58 | externals, 59 | resolve: { 60 | extensions: ['.js', '.ts'] 61 | }, 62 | module: { 63 | rules: [ 64 | { 65 | test: /\.ts$/, 66 | use: ['awesome-typescript-loader', 'angular2-template-loader'], 67 | exclude: [/\.(spec|e2e)\.ts$/] 68 | }, 69 | { 70 | test: /\.html$/, 71 | use: 'raw-loader' 72 | }, 73 | { 74 | test: /\.scss$/, 75 | use: ['raw-loader', 'sass-loader'] 76 | }, 77 | { 78 | test: /\.css$/, 79 | use: ['raw-loader', 'style-loader'] 80 | } 81 | ] 82 | }, 83 | plugins: [ 84 | // Generates an AoT JavaScript bundle. 85 | // TODO: Remove this in favor of Angular's native library bundler, 86 | // once we've upgraded to Angular version 6. 87 | new ngtools.AotPlugin({ 88 | tsConfigPath: skyPagesConfigUtil.spaPathTemp('tsconfig.json'), 89 | entryModule: skyPagesConfigUtil.spaPathTemp('main.ts') + '#SkyLibPlaceholderModule', 90 | sourceMap: true 91 | }), 92 | 93 | new webpack.optimize.UglifyJsPlugin({ 94 | beautify: false, 95 | comments: false, 96 | compress: { warnings: false }, 97 | mangle: { screw_ie8: true, keep_fnames: true } 98 | }) 99 | ] 100 | }; 101 | } 102 | 103 | module.exports = { 104 | getWebpackConfig: getWebpackConfig 105 | }; 106 | -------------------------------------------------------------------------------- /config/webpack/build.webpack.config.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const webpack = require('webpack'); 5 | const webpackMerge = require('webpack-merge'); 6 | const SaveMetadata = require('../../plugin/save-metadata'); 7 | 8 | /** 9 | * Returns the default webpackConfig. 10 | * @name getDefaultWebpackConfig 11 | * @returns {WebpackConfig} webpackConfig 12 | */ 13 | function getWebpackConfig(skyPagesConfig, argv) { 14 | const common = require('./common.webpack.config'); 15 | 16 | return webpackMerge(common.getWebpackConfig(skyPagesConfig, argv), { 17 | devtool: 'source-map', 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.ts$/, 22 | use: [ 23 | { 24 | loader: 'awesome-typescript-loader', 25 | options: { 26 | // Ignore the "Cannot find module" error that occurs when referencing 27 | // an aliased file. Webpack will still throw an error when a module 28 | // cannot be resolved via a file path or alias. 29 | ignoreDiagnostics: [2307], 30 | transpileOnly: true 31 | } 32 | }, 33 | { 34 | loader: 'angular2-template-loader' 35 | } 36 | ] 37 | } 38 | ] 39 | }, 40 | plugins: [ 41 | SaveMetadata, 42 | new webpack.optimize.UglifyJsPlugin({ 43 | beautify: false, 44 | comments: false, 45 | mangle: { screw_ie8: true, keep_fnames: true }, 46 | sourceMap: true 47 | }) 48 | ] 49 | }); 50 | } 51 | 52 | module.exports = { 53 | getWebpackConfig: getWebpackConfig 54 | }; 55 | -------------------------------------------------------------------------------- /config/webpack/serve.webpack.config.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const webpackMerge = require('webpack-merge'); 7 | const NamedModulesPlugin = require('webpack/lib/NamedModulesPlugin'); 8 | const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin'); 9 | const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin'); 10 | 11 | const skyPagesConfigUtil = require('../sky-pages/sky-pages.config'); 12 | const browser = require('../../cli/utils/browser'); 13 | 14 | /** 15 | * Returns the default webpackConfig. 16 | * @name getDefaultWebpackConfig 17 | * @returns {WebpackConfig} webpackConfig 18 | */ 19 | function getWebpackConfig(argv, skyPagesConfig) { 20 | 21 | /** 22 | * Opens the host service url. 23 | * @name WebpackPluginDone 24 | */ 25 | function WebpackPluginDone() { 26 | 27 | let launched = false; 28 | this.plugin('done', (stats) => { 29 | if (!launched) { 30 | launched = true; 31 | browser(argv, skyPagesConfig, stats, this.options.devServer.port); 32 | } 33 | }); 34 | } 35 | 36 | const common = require('./common.webpack.config').getWebpackConfig(skyPagesConfig, argv); 37 | 38 | return webpackMerge(common, { 39 | watch: true, 40 | module: { 41 | rules: [ 42 | { 43 | test: /\.ts$/, 44 | use: [ 45 | { 46 | loader: 'awesome-typescript-loader', 47 | options: { 48 | // Ignore the "Cannot find module" error that occurs when referencing 49 | // an aliased file. Webpack will still throw an error when a module 50 | // cannot be resolved via a file path or alias. 51 | ignoreDiagnostics: [2307], 52 | transpileOnly: true, 53 | silent: true 54 | } 55 | }, 56 | { 57 | loader: 'angular2-template-loader' 58 | } 59 | ], 60 | exclude: [/\.e2e\.ts$/] 61 | } 62 | ] 63 | }, 64 | devServer: { 65 | compress: true, 66 | inline: true, 67 | stats: false, 68 | hot: argv.hmr, 69 | contentBase: path.join(process.cwd(), 'src', 'app'), 70 | headers: { 71 | 'Access-Control-Allow-Origin': '*' 72 | }, 73 | historyApiFallback: { 74 | index: skyPagesConfigUtil.getAppBase(skyPagesConfig) 75 | }, 76 | https: { 77 | key: fs.readFileSync(path.join(__dirname, '../../ssl/server.key')), 78 | cert: fs.readFileSync(path.join(__dirname, '../../ssl/server.crt')) 79 | }, 80 | publicPath: skyPagesConfigUtil.getAppBase(skyPagesConfig) 81 | }, 82 | devtool: 'source-map', 83 | plugins: [ 84 | new NamedModulesPlugin(), 85 | WebpackPluginDone, 86 | new LoaderOptionsPlugin({ 87 | context: __dirname, 88 | debug: true 89 | }), 90 | new HotModuleReplacementPlugin() 91 | ] 92 | }); 93 | } 94 | 95 | module.exports = { 96 | getWebpackConfig: getWebpackConfig 97 | }; 98 | -------------------------------------------------------------------------------- /e2e/shared/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const minimist = require('minimist'); 5 | const cli = require('../../.e2e-tmp/node_modules/@blackbaud/skyux-builder/index'); 6 | const argv = minimist(process.argv.slice(2)); 7 | 8 | cli.runCommand(argv._[0], argv); 9 | -------------------------------------------------------------------------------- /e2e/shared/tests.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | /*global element, by, $$, protractor, browser*/ 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const common = require('./common'); 8 | 9 | module.exports = { 10 | 11 | verifyExitCode: (done) => { 12 | expect(common.getExitCode()).toEqual(0); 13 | done(); 14 | }, 15 | 16 | verifyFiles: (done) => { 17 | [ 18 | 'app.js', 19 | 'index.html', 20 | 'metadata.json', 21 | 'polyfills.js', 22 | 'skyux.js', 23 | 'vendor.js' 24 | ].forEach(file => { 25 | expect(fs.existsSync(path.resolve(common.tmp, 'dist', file))).toEqual(true); 26 | }); 27 | done(); 28 | }, 29 | 30 | renderHomeComponent: (done) => { 31 | expect(element(by.tagName('h1')).getText()).toBe('SKY UX Template'); 32 | expect(element(by.className('sky-alert')).getText()).toBe( 33 | `You've just taken your first step into a larger world.` 34 | ); 35 | done(); 36 | }, 37 | 38 | renderSharedNavComponent: (done) => { 39 | const nav = $$('.sky-navbar-item'); 40 | expect(nav.count()).toBe(2); 41 | done(); 42 | }, 43 | 44 | followRouterLinkRenderAbout: (done) => { 45 | const nav = $$('.sky-navbar-item a'); 46 | nav.get(1).click(); 47 | expect(element(by.tagName('h1')).getText()).toBe('About our Team'); 48 | done(); 49 | }, 50 | 51 | respectGuardCanActivate: (done) => { 52 | const nav = $$('.sky-navbar-item a'); 53 | nav.get(1).click(); 54 | expect(element(by.tagName('h1')).getText()).toBe('SKY UX Template'); 55 | 56 | const aboutComponent = $$('my-about')[0]; 57 | expect(aboutComponent).toBe(undefined); 58 | 59 | done(); 60 | }, 61 | 62 | respectRootGuard: (done) => { 63 | // if the home component isn't there, the outlet was not 64 | // allowed to activate due to the Guard! 65 | const homeComponent = $$('my-home')[0]; 66 | expect(homeComponent).toBe(undefined); 67 | done(); 68 | }, 69 | 70 | verifyChildRoute: (done) => { 71 | $$('#test').get(0).click(); 72 | expect($$('h1').get(0).getText()).toBe('Hi'); 73 | done(); 74 | }, 75 | 76 | verifyNestedChildRoute: (done) => { 77 | $$('#child').get(0).click(); 78 | 79 | expect($$('h1').get(0).getText()).toBe('Hi'); 80 | expect($$('#text').get(0).getText()).toBe('Child'); 81 | done(); 82 | }, 83 | 84 | verifyNestedTopRoute: (done) => { 85 | $$('#top').get(0).click(); 86 | 87 | expect($$('h1')[0]).toBe(undefined); 88 | expect($$('#text').get(0).getText()).toBe('Top'); 89 | done(); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /e2e/skyux-build-aot.e2e-spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | const common = require('./shared/common'); 4 | const tests = require('./shared/tests'); 5 | 6 | function prepareBuild() { 7 | const opts = { mode: 'easy', name: 'dist', compileMode: 'aot' }; 8 | return common.prepareBuild(opts) 9 | .catch(console.error); 10 | } 11 | 12 | describe('skyux build aot', () => { 13 | describe('w/base template', () => { 14 | beforeAll((done) => prepareBuild().then(done)); 15 | 16 | it('should have exitCode 0', tests.verifyExitCode); 17 | 18 | it('should generate expected static files', tests.verifyFiles); 19 | 20 | it('should render the home components', tests.renderHomeComponent); 21 | 22 | it('should render shared nav component', tests.renderSharedNavComponent); 23 | 24 | it('should follow routerLink and render about component', tests.followRouterLinkRenderAbout); 25 | 26 | afterAll(common.afterAll); 27 | }); 28 | 29 | describe('w/guard', () => { 30 | beforeAll((done) => { 31 | const guard = ` 32 | import { Injectable } from '@angular/core'; 33 | 34 | @Injectable() 35 | export class AboutGuard { 36 | public canActivate(next: any, state: any): boolean { 37 | return false; 38 | } 39 | } 40 | `; 41 | 42 | common.writeAppFile('about/index.guard.ts', guard) 43 | .then(() => prepareBuild()) 44 | .then(done) 45 | .catch(console.error); 46 | }); 47 | 48 | it('should not follow routerLink when guard returns false', tests.respectGuardCanActivate); 49 | 50 | afterAll((done) => { 51 | common.removeAppFolderItem('about/index.guard.ts') 52 | .then(() => common.afterAll()) 53 | .then(done) 54 | .catch(console.error); 55 | }); 56 | }); 57 | 58 | describe('w/root level guard', () => { 59 | beforeAll((done) => { 60 | const guard = ` 61 | import { Injectable } from '@angular/core'; 62 | 63 | @Injectable() 64 | export class RootGuard { 65 | public canActivateChild(next: any, state: any): boolean { 66 | return false; 67 | } 68 | } 69 | `; 70 | 71 | common.writeAppFile('index.guard.ts', guard) 72 | .then(() => prepareBuild()) 73 | .then(done) 74 | .catch(console.error); 75 | }); 76 | 77 | it('should respect root guard', tests.respectRootGuard); 78 | 79 | afterAll((done) => { 80 | common.removeAppFolderItem('index.guard.ts') 81 | .then(() => common.afterAll()) 82 | .then(done) 83 | .catch(console.error); 84 | }); 85 | }); 86 | 87 | describe('w/child routes', () => { 88 | beforeAll((done) => { 89 | common.verifyAppFolder('test') 90 | .then(() => common.writeAppFile('index.html', 'Test')) 91 | .then(() => common.writeAppFile( 92 | 'test/index.html', 93 | '

Hi

' + 94 | 'Child' + 95 | 'Top' + 96 | '') 97 | ) 98 | .then(() => common.verifyAppFolder('test/#child')) 99 | .then(() => common.writeAppFile('test/#child/index.html', '
Child
')) 100 | .then(() => common.verifyAppFolder('test/#child/top')) 101 | .then(() => common.writeAppFile('test/#child/top/index.html', '
Top
')) 102 | .then(() => prepareBuild()) 103 | .then(done) 104 | .catch(console.error); 105 | }); 106 | 107 | it('should have working child route', tests.verifyChildRoute); 108 | 109 | it('should have working nested child route', tests.verifyNestedChildRoute); 110 | 111 | it('should have working top level route inside child route folder', tests.verifyNestedTopRoute); 112 | 113 | afterAll((done) => { 114 | common.removeAppFolderItem('test/#child/top/index.html') 115 | .then(() => common.writeAppFile('index.html', '')) 116 | .then(() => common.removeAppFolderItem('test')) 117 | .then(() => common.afterAll()) 118 | .then(done) 119 | .catch(console.error); 120 | }); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /e2e/skyux-build-jit.e2e-spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | const common = require('./shared/common'); 4 | const tests = require('./shared/tests'); 5 | 6 | function prepareBuild() { 7 | const opts = { mode: 'easy', name: 'dist', compileMode: 'jit' }; 8 | return common.prepareBuild(opts) 9 | .catch(err => console.error); 10 | } 11 | 12 | describe('skyux build jit', () => { 13 | describe('w/base template', () => { 14 | beforeAll((done) => prepareBuild().then(done)); 15 | 16 | it('should have exitCode 0', tests.verifyExitCode); 17 | 18 | it('should generate expected static files', tests.verifyFiles); 19 | 20 | it('should render the home components', tests.renderHomeComponent); 21 | 22 | it('should render shared nav component', tests.renderSharedNavComponent); 23 | 24 | it('should follow routerLink and render about component', tests.followRouterLinkRenderAbout); 25 | 26 | afterAll(common.afterAll); 27 | }); 28 | 29 | describe('w/guard', () => { 30 | beforeAll((done) => { 31 | const guard = ` 32 | import { Injectable } from '@angular/core'; 33 | 34 | @Injectable() 35 | export class AboutGuard { 36 | public canActivate(next: any, state: any): boolean { 37 | return false; 38 | } 39 | } 40 | `; 41 | 42 | common.writeAppFile('about/index.guard.ts', guard) 43 | .then(() => prepareBuild()) 44 | .then(done) 45 | .catch(console.error); 46 | }); 47 | 48 | it('should not follow routerLink when guard returns false', tests.respectGuardCanActivate); 49 | 50 | afterAll((done) => { 51 | common.removeAppFolderItem('about/index.guard.ts') 52 | .then(() => common.afterAll()) 53 | .then(done) 54 | .catch(console.error); 55 | }); 56 | }); 57 | 58 | describe('w/root level guard', () => { 59 | beforeAll((done) => { 60 | const guard = ` 61 | import { Injectable } from '@angular/core'; 62 | 63 | @Injectable() 64 | export class RootGuard { 65 | public canActivateChild(next: any, state: any) { 66 | return false; 67 | } 68 | } 69 | `; 70 | 71 | common.writeAppFile('index.guard.ts', guard) 72 | .then(() => prepareBuild()) 73 | .then(done) 74 | .catch(console.error); 75 | }); 76 | 77 | it('should respect root guard', tests.respectRootGuard); 78 | 79 | afterAll((done) => { 80 | common.removeAppFolderItem('index.guard.ts') 81 | .then(() => common.afterAll()) 82 | .then(done) 83 | .catch(console.error); 84 | }); 85 | }); 86 | 87 | describe('w/child routes', () => { 88 | beforeAll((done) => { 89 | common.verifyAppFolder('test') 90 | .then(() => common.writeAppFile('index.html', 'Test')) 91 | .then(() => common.writeAppFile( 92 | 'test/index.html', 93 | '

Hi

' + 94 | 'Child' + 95 | 'Top' + 96 | '') 97 | ) 98 | .then(() => common.verifyAppFolder('test/#child')) 99 | .then(() => common.writeAppFile('test/#child/index.html', '
Child
')) 100 | .then(() => common.verifyAppFolder('test/#child/top')) 101 | .then(() => common.writeAppFile('test/#child/top/index.html', '
Top
')) 102 | .then(() => prepareBuild()) 103 | .then(done) 104 | .catch(console.error); 105 | }); 106 | 107 | it('should have working child route', tests.verifyChildRoute); 108 | 109 | it('should have working nested child route', tests.verifyNestedChildRoute); 110 | 111 | it('should have working top level route inside child route folder', tests.verifyNestedTopRoute); 112 | 113 | afterAll((done) => { 114 | common.removeAppFolderItem('test/#child/top/index.html') 115 | .then(() => common.writeAppFile('index.html', '')) 116 | .then(() => common.removeAppFolderItem('test')) 117 | .then(() => common.afterAll()) 118 | .then(done) 119 | .catch(console.error); 120 | }); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /e2e/skyux-e2e.e2e-spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const common = require('./shared/common'); 5 | 6 | function validateTestRun(done) { 7 | common.exec(`node`, [common.cliPath, `e2e`, `--logFormat`, `none`], common.cwdOpts) 8 | .then(exit => { 9 | expect(exit).toEqual(0); 10 | done(); 11 | }) 12 | .catch((err) => { 13 | console.log(err); 14 | done(); 15 | }); 16 | } 17 | 18 | describe('skyux e2e', () => { 19 | it('should successfully run e2e tests', (done) => { 20 | validateTestRun(done); 21 | }); 22 | 23 | describe('with auth', () => { 24 | beforeAll((done) => { 25 | const opts = { mode: 'easy', name: 'dist', auth: true }; 26 | common.prepareBuild(opts) 27 | .then(done) 28 | .catch(err => { 29 | console.log(err); 30 | done(); 31 | }); 32 | }); 33 | 34 | afterAll(common.afterAll); 35 | 36 | it('should successfully run e2e tests', (done) => { 37 | validateTestRun(done); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /e2e/skyux-lib-help-tests/fixtures/skyux-modal/modal-form-fixture.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { SkyModalInstance } from '@blackbaud/skyux/dist/core'; 4 | 5 | @Component({ 6 | selector: 'sky-modal-form', 7 | template: ` 8 | 9 | 10 | Mock Modal 11 | 12 | 13 | 14 | 15 | 17 | 18 | ` 19 | }) 20 | export class SkyModalDemoFormComponent { 21 | constructor(public instance: SkyModalInstance) { } 22 | } 23 | -------------------------------------------------------------------------------- /e2e/skyux-lib-help-tests/fixtures/skyux-modal/modal-launcher-fixture.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { SkyModalService } from '@blackbaud/skyux/dist/core'; 4 | 5 | import { SkyModalDemoFormComponent } from './modal-form-fixture.component'; 6 | 7 | import { HelpWidgetService } from '@blackbaud/skyux-lib-help'; 8 | 9 | @Component({ 10 | selector: 'help-modal-launcher', 11 | template: ` 12 | 15 | 16 | ` 19 | }) 20 | export class HelpModalDemoComponent { 21 | constructor( 22 | private helpService: HelpWidgetService, 23 | private modal: SkyModalService) { } 24 | 25 | public openModal(modalType: string) { 26 | 27 | let modalOptions = { 28 | fullPage: false, 29 | helpKey: 'modal-header' 30 | }; 31 | 32 | switch (modalType) { 33 | case 'fullPage': 34 | modalOptions.fullPage = true; 35 | break; 36 | default: 37 | break; 38 | } 39 | 40 | let modalInstance = this.modal.open(SkyModalDemoFormComponent, modalOptions); 41 | 42 | modalInstance.helpOpened.subscribe((helpKey: string) => { 43 | this.helpService.openToHelpKey(helpKey); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /e2e/skyux-lib-help-tests/skyux-lib-help.e2e-spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | /*global browser, element, by, $$*/ 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const common = require('../shared/common'); 8 | const tests = require('../shared/tests'); 9 | 10 | const tmpSrcApp = path.resolve(process.cwd(), common.tmp, 'src/app'); 11 | const e2eRootPath = path.resolve(process.cwd(), 'e2e/skyux-lib-help-tests'); 12 | 13 | let originalHomePage; 14 | 15 | // Add the SkyModalDemoFormComponent to the entryComponents in the app-extras module. 16 | const mockAppExtras = ` 17 | import { NgModule } from '@angular/core'; 18 | 19 | import { SkyModalDemoFormComponent } from './modal-fixtures/modal-form-fixture.component'; 20 | 21 | @NgModule({ 22 | providers: [], 23 | entryComponents: [ 24 | SkyModalDemoFormComponent 25 | ] 26 | }) 27 | export class AppExtrasModule { } 28 | `; 29 | 30 | function prepareBuild() { 31 | const configOptions = { 32 | mode: 'easy', 33 | name: 'dist', 34 | compileMode: 'aot', 35 | help: { 36 | extends: 'bb-help' 37 | } 38 | }; 39 | 40 | return common.prepareBuild(configOptions) 41 | .catch(console.error); 42 | } 43 | 44 | function migrateFixtures() { 45 | const files = fs.readdirSync(`${e2eRootPath}/fixtures/skyux-modal`); 46 | 47 | if (!fs.existsSync(`${tmpSrcApp}/modal-fixtures`)) { 48 | fs.mkdirSync(`${tmpSrcApp}/modal-fixtures`); 49 | } 50 | 51 | files.forEach(file => { 52 | const filePath = path.resolve(`${e2eRootPath}/fixtures/skyux-modal`, file); 53 | const content = fs.readFileSync(filePath, 'utf8'); 54 | common.writeAppFile(`modal-fixtures/${file}`, content); 55 | }); 56 | } 57 | 58 | function addModalToHomePage() { 59 | migrateFixtures(); 60 | if (!originalHomePage) { 61 | originalHomePage = fs.readFileSync(`${tmpSrcApp}/home.component.html`, 'utf8'); 62 | } 63 | 64 | common.writeAppExtras(mockAppExtras); 65 | const content = ``; 66 | common.writeAppFile('home.component.html', content, 'utf8'); 67 | } 68 | 69 | describe('skyux lib help', () => { 70 | beforeAll((done) => { 71 | prepareBuild() 72 | .then(() => { 73 | done(); 74 | }); 75 | addModalToHomePage(); 76 | }); 77 | 78 | afterAll(() => { 79 | common.writeAppFile('home.component.html', originalHomePage, 'utf8'); 80 | common.removeAppFolderItem('modal-fixtures'); 81 | common.afterAll(); 82 | }); 83 | 84 | /** 85 | * SKY UX adds the class 'sky-modal-body-full-page' to the body tag when a full page modal is 86 | * launched. In order to hide the invoker tab when a full page modal is present, we added a style 87 | * to the app.component.scss file in builder to target the '#bb-help-container.bb-help-closed' 88 | * selector and add a display: none to the invoker. This test is to confirm that neither library 89 | * changed the class names that accomplish this style override. 90 | */ 91 | it('should hide the invoker when a full page modal is opened', () => { 92 | let invoker = element(by.id('bb-help-invoker')); 93 | let regularModalButton = element(by.id('regular-modal-launcher')); 94 | let fullPageButton = element(by.id('full-page-modal-launcher')); 95 | 96 | expect(invoker.isDisplayed()).toBe(true); 97 | 98 | regularModalButton.click(); 99 | expect(invoker.isDisplayed()).toBe(true); 100 | element(by.id('modal-close-button')).click(); 101 | 102 | fullPageButton.click(); 103 | expect(invoker.isDisplayed()).toBe(false); 104 | element(by.id('modal-close-button')).click(); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /e2e/skyux-serve.e2e-spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | /*global browser, element, by, $$*/ 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const common = require('./shared/common'); 8 | const tests = require('./shared/tests'); 9 | 10 | const timestamp = new Date().getTime(); 11 | 12 | describe('skyux serve', () => { 13 | 14 | let url; 15 | 16 | beforeAll((done) => { 17 | common.prepareServe().then((port) => { 18 | url = `https://localhost:${port}/rrrrr-app-name/`; 19 | browser.get(url) 20 | .then(done) 21 | .catch(err => { 22 | console.log(err); 23 | done(); 24 | }); 25 | }, common.catchReject); 26 | }); 27 | 28 | afterAll(common.afterAll); 29 | 30 | it('should render home components', tests.renderHomeComponent); 31 | it('should render shared nav component', tests.renderSharedNavComponent); 32 | it('should follow routerLink and render about component', tests.followRouterLinkRenderAbout); 33 | 34 | it('should should include script tags', (done) => { 35 | browser.getPageSource() 36 | .then(source => { 37 | let previousIndex = -1; 38 | [ 39 | 'polyfills.js', 40 | 'vendor.js', 41 | 'skyux.js', 42 | 'app.js' 43 | ].forEach(file => { 44 | const currentIndex = source.indexOf(``); 45 | expect(currentIndex).toBeGreaterThan(previousIndex); 46 | previousIndex = currentIndex; 47 | }); 48 | 49 | done(); 50 | }) 51 | .catch(err => { 52 | console.log(err); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('should watch for existing file changes', (done) => { 58 | const file = path.resolve(common.tmp, 'src', 'app', 'home.component.html'); 59 | const content = fs.readFileSync(file, 'utf8'); 60 | 61 | common.bindServe().then(() => { 62 | browser.get(url) 63 | .then(() => { 64 | $$('#ts').getText().then((tsResult) => { 65 | expect(tsResult[0]).toEqual(timestamp.toString()); 66 | fs.writeFileSync(file, content, 'utf8'); 67 | done(); 68 | }); 69 | 70 | }) 71 | .catch(err => { 72 | console.log(err); 73 | done(); 74 | }); 75 | }, common.catchReject); 76 | 77 | fs.writeFileSync(file, `${content}\n

${timestamp}

`, 'utf8'); 78 | }); 79 | 80 | it('should watch for new files', (done) => { 81 | const folder = path.resolve(common.tmp, 'src', 'app', 'test-dir'); 82 | const file = path.join(folder, 'index.html'); 83 | const message = `Test Message`; 84 | const tag = `h1`; 85 | 86 | common.bindServe().then(() => { 87 | browser.get(`${url}test-dir`) 88 | .then(() => { 89 | expect(element(by.tagName(tag)).getText()).toBe(message); 90 | fs.unlinkSync(file); 91 | fs.rmdirSync(folder); 92 | done(); 93 | }) 94 | .catch(err => { 95 | console.log(err); 96 | done(); 97 | }); 98 | }, common.catchReject); 99 | 100 | if (!fs.existsSync(folder)) { 101 | fs.mkdirSync(folder); 102 | } 103 | 104 | fs.writeFileSync(file, `<${tag}>${message}`, 'utf8'); 105 | }); 106 | 107 | }); 108 | -------------------------------------------------------------------------------- /e2e/skyux-test.e2e-spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const common = require('./shared/common'); 5 | 6 | describe('skyux test', () => { 7 | it('should successfully run unit tests', (done) => { 8 | common.exec(`node`, [common.cliPath, `test`], common.cwdOpts) 9 | .then(exit => { 10 | expect(exit).toEqual(0); 11 | done(); 12 | }) 13 | .catch(err => { 14 | console.log(err); 15 | done(); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare var SKY_PAGES: any; 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const webpack = require('webpack'); 5 | const WebpackDevServer = require('webpack-dev-server'); 6 | const config = require('./config/sky-pages/sky-pages.config'); 7 | 8 | // Used to suppress logging unless it's a known command 9 | function getConfig(command) { 10 | return config.getSkyPagesConfig(command); 11 | } 12 | 13 | module.exports = { 14 | runCommand: (command, argv) => { 15 | const shorthand = { 16 | f: 'force', 17 | l: 'launch', 18 | b: 'browser', 19 | s: 'serve' 20 | }; 21 | 22 | // Process shorthand flags 23 | Object.keys(shorthand).forEach(key => { 24 | if (argv[key]) { 25 | argv[shorthand[key]] = argv[key]; 26 | } 27 | }); 28 | 29 | switch (command) { 30 | case 'build': 31 | require('./cli/build')(argv, getConfig(command), webpack); 32 | break; 33 | case 'build-public-library': 34 | require('./cli/build-public-library')(getConfig(command), webpack); 35 | break; 36 | case 'e2e': 37 | require('./cli/e2e')(command, argv, getConfig(command), webpack); 38 | break; 39 | case 'serve': 40 | require('./cli/serve')(argv, getConfig(command), webpack, WebpackDevServer); 41 | break; 42 | case 'lint': 43 | require('./cli/lint')(); 44 | break; 45 | case 'pact': 46 | require('./cli/pact')(command, argv); 47 | break; 48 | case 'test': 49 | case 'watch': 50 | require('./cli/test')(command, argv); 51 | break; 52 | case 'version': 53 | require('./cli/version')(); 54 | break; 55 | case 'generate': 56 | case 'g': 57 | require('./cli/generate')(argv); 58 | break; 59 | default: 60 | return false; 61 | } 62 | 63 | return true; 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "test", 3 | "spec_files": [ 4 | "*.spec.js" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /lib/assets-processor.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const skyPagesConfigUtil = require('../config/sky-pages/sky-pages.config'); 5 | const assetsUtils = require('../utils/assets-utils'); 6 | 7 | const { 8 | isLocaleFile, 9 | resolvePhysicalLocaleFilePath, 10 | resolveRelativeLocaleFileDestination 11 | } = require('./locale-assets-processor'); 12 | 13 | const ASSETS_REGEX = /~\/assets\/.*?\.[\.\w]+/gi; 14 | 15 | /** 16 | * Gets the assets URL with the application's root directory appended to it. 17 | * @param {*} skyPagesConfig The SKY UX app config. 18 | * @param {*} baseUrl The base URL where the assets will be served. 19 | * @param {*} rel An additional relative path to append to the assets base URL 20 | * once the application's root directory has been appended. 21 | */ 22 | function getAssetsUrl(skyPagesConfig, baseUrl, rel) { 23 | return baseUrl + skyPagesConfig.runtime.app.base + (rel || ''); 24 | } 25 | 26 | /** 27 | * Finds referenced assets in a file and replaces occurrences in the file's 28 | * contents with the absolute URL. 29 | * @param {*} content The file contents. 30 | * @param {*} baseUrl The base URL where the assets will be served. 31 | * @param {*} callback A function to call for each found asset path. The function will be 32 | * provided the file path with a file hash added to the file name along with the 33 | * physical path to the file. 34 | */ 35 | function processAssets(content, baseUrl, callback) { 36 | let match = ASSETS_REGEX.exec(content); 37 | 38 | while (match) { 39 | const matchString = match[0]; 40 | 41 | let filePath; 42 | let filePathWithHash; 43 | 44 | if (isLocaleFile(matchString)) { 45 | filePath = resolvePhysicalLocaleFilePath(matchString); 46 | filePathWithHash = resolveRelativeLocaleFileDestination( 47 | assetsUtils.getFilePathWithHash(filePath, true) 48 | ); 49 | } else { 50 | filePath = matchString.substring(2, matchString.length); 51 | filePathWithHash = assetsUtils.getFilePathWithHash(filePath, true); 52 | } 53 | 54 | if (callback) { 55 | callback(filePathWithHash, skyPagesConfigUtil.spaPath('src', filePath)); 56 | } 57 | 58 | const url = `${baseUrl}${filePathWithHash.replace(/\\/gi, '/')}`; 59 | 60 | content = content.replace( 61 | new RegExp(matchString, 'gi'), 62 | url 63 | ); 64 | 65 | match = ASSETS_REGEX.exec(content); 66 | } 67 | 68 | return content; 69 | } 70 | 71 | /** 72 | * Sets the assets URL on Webpack loaders that reference it. 73 | * @param {*} webpackConfig The Webpack config object. 74 | * @param {*} skyPagesConfig The SKY UX app config. 75 | * @param {*} baseUrl The base URL where the assets will be served. 76 | * @param {*} rel An additional relative path to append to the assets base URL 77 | * once the application's root directory has been appended. 78 | */ 79 | function setSkyAssetsLoaderUrl(webpackConfig, skyPagesConfig, baseUrl, rel) { 80 | const rules = webpackConfig && 81 | webpackConfig.module && 82 | webpackConfig.module.rules; 83 | 84 | if (rules) { 85 | const assetsRule = rules.find(rule => /sky-assets$/.test(rule.loader)); 86 | assetsRule.options = assetsRule.options || {}; 87 | assetsRule.options.baseUrl = getAssetsUrl(skyPagesConfig, baseUrl, rel); 88 | } 89 | } 90 | 91 | module.exports = { 92 | getAssetsUrl, 93 | setSkyAssetsLoaderUrl, 94 | processAssets 95 | }; 96 | -------------------------------------------------------------------------------- /lib/locale-assets-processor.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const glob = require('glob'); 6 | const path = require('path'); 7 | 8 | const { spaPath } = require('../config/sky-pages/sky-pages.config'); 9 | 10 | const localesPath = ['src', 'assets', 'locales']; 11 | const defaultLocaleFileName = 'resources_en_US.json'; 12 | const defaultFile = tempPath(defaultLocaleFileName); 13 | 14 | const libPaths = [ 15 | spaPath( 16 | 'node_modules', 17 | '@blackbaud', 18 | '**', 19 | ...localesPath 20 | ), 21 | spaPath( 22 | 'node_modules', 23 | '@blackbaud-internal', 24 | '**', 25 | ...localesPath 26 | ), 27 | spaPath( 28 | 'node_modules', 29 | '@skyux', 30 | '**', 31 | ...localesPath 32 | ) 33 | ]; 34 | 35 | function tempPath(...args) { 36 | return spaPath('.skypageslocales', ...args); 37 | } 38 | 39 | function readJson(file) { 40 | fs.ensureFileSync(file); 41 | 42 | const buffer = fs.readFileSync(file); 43 | 44 | let contents; 45 | // Is the locale file empty? 46 | if (buffer.length === 0) { 47 | contents = {}; 48 | } else { 49 | contents = JSON.parse(buffer.toString()); 50 | } 51 | 52 | return contents; 53 | } 54 | 55 | function extendJson(...files) { 56 | return files.reduce((accumulator, file) => 57 | Object.assign(accumulator, readJson(file)), 58 | {} 59 | ); 60 | } 61 | 62 | function isLocaleFile(file) { 63 | return /resources_[a-z]+(\-|_)+[A-Z]+\.json$/.test(file); 64 | } 65 | 66 | function parseLocaleFileBasename(filePath) { 67 | return path.basename(filePath).replace(/\-/g, '_'); 68 | } 69 | 70 | function resolvePhysicalLocaleFilePath(filePath) { 71 | return tempPath(parseLocaleFileBasename(filePath)); 72 | } 73 | 74 | function resolveRelativeLocaleFileDestination(filePath) { 75 | return path.join( 76 | 'assets', 77 | 'locales', 78 | parseLocaleFileBasename(filePath) 79 | ); 80 | } 81 | 82 | function getDefaultLocaleFiles(dirname) { 83 | return glob.sync( 84 | path.join(dirname, '@(resources_en_US.json|resources_en-US.json)') 85 | ); 86 | } 87 | 88 | function getNonDefaultLocaleFiles(dirname) { 89 | return glob.sync( 90 | path.join(dirname, 'resources_*.json'), 91 | { 92 | ignore: `**/${defaultLocaleFileName}` 93 | } 94 | ); 95 | } 96 | 97 | function mergeDefaultLocaleFiles() { 98 | const libFiles = libPaths.reduce((accumulator, libPath) => 99 | accumulator.concat( 100 | getDefaultLocaleFiles(libPath) 101 | ), []); 102 | const contents = extendJson(...libFiles, defaultFile); 103 | fs.writeJsonSync(defaultFile, contents); 104 | } 105 | 106 | function mergeNonDefaultLocaleFiles() { 107 | // Extend all SPA files with contents of default locale file. 108 | getNonDefaultLocaleFiles(tempPath()) 109 | .forEach(file => { 110 | const contents = extendJson(defaultFile, file); 111 | fs.writeJsonSync(file, contents); 112 | }); 113 | 114 | // Extend all SPA files with library files. 115 | libPaths.reduce((accumulator, libPath) => 116 | accumulator.concat( 117 | getNonDefaultLocaleFiles(libPath) 118 | ), []) 119 | .forEach(libFile => { 120 | const basename = path.basename(libFile); 121 | const spaFile = tempPath(basename); 122 | const contents = extendJson(defaultFile, spaFile, libFile); 123 | fs.writeJsonSync(spaFile, contents); 124 | }); 125 | } 126 | 127 | function stageLocaleFiles() { 128 | const temp = tempPath(); 129 | 130 | fs.ensureDirSync(temp); 131 | fs.emptyDirSync(temp); 132 | 133 | // Copy all SPA locale files to the temp directory. 134 | glob.sync(spaPath(...localesPath, 'resources_*.json')) 135 | .forEach(filePath => { 136 | const basename = parseLocaleFileBasename(filePath); 137 | fs.copySync(filePath, tempPath(basename)); 138 | }); 139 | } 140 | 141 | function prepareLocaleFiles() { 142 | stageLocaleFiles(); 143 | mergeDefaultLocaleFiles(); 144 | mergeNonDefaultLocaleFiles(); 145 | } 146 | 147 | function removeLocaleFiles() { 148 | fs.removeSync(tempPath()); 149 | } 150 | 151 | process.on('exit', () => { 152 | removeLocaleFiles(); 153 | }); 154 | 155 | process.on('SIGINT', () => { 156 | process.exit(); 157 | }); 158 | 159 | module.exports = { 160 | getDefaultLocaleFiles, 161 | isLocaleFile, 162 | parseLocaleFileBasename, 163 | prepareLocaleFiles, 164 | resolvePhysicalLocaleFilePath, 165 | resolveRelativeLocaleFileDestination 166 | }; 167 | -------------------------------------------------------------------------------- /lib/plugin-file-processor.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true */ 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const glob = require('glob'); 6 | 7 | const skyPagesConfigUtil = require('../config/sky-pages/sky-pages.config'); 8 | const processor = require('../loader/sky-processor'); 9 | 10 | const processFiles = ( 11 | skyPagesConfig, 12 | rootDir = skyPagesConfigUtil.spaPathTempSrc('app', '**', '*.*') 13 | ) => { 14 | const filePaths = glob.sync(rootDir, { 15 | nodir: true 16 | }); 17 | 18 | filePaths.forEach(filePath => { 19 | const contents = fs.readFileSync(filePath); 20 | const altered = processor.preload(contents, { 21 | resourcePath: filePath, 22 | options: { 23 | skyPagesConfig 24 | } 25 | }); 26 | 27 | if (contents !== altered) { 28 | fs.writeFileSync(filePath, altered, { encoding: 'utf8' }); 29 | } 30 | }); 31 | }; 32 | 33 | module.exports = { processFiles }; 34 | -------------------------------------------------------------------------------- /lib/sky-pages-assets-generator.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const glob = require('glob'); 5 | 6 | const skyPagesConfigUtil = require('../config/sky-pages/sky-pages.config'); 7 | const codegen = require('../utils/codegen-utils'); 8 | 9 | const localeAssetsProcessor = require('./locale-assets-processor'); 10 | 11 | function getClassName() { 12 | return 'SkyAppAssetsImplService'; 13 | } 14 | 15 | function getSource() { 16 | const srcPath = skyPagesConfigUtil.spaPath('src'); 17 | const assetsPath = skyPagesConfigUtil.spaPath('src', 'assets'); 18 | 19 | let filePaths = glob.sync( 20 | skyPagesConfigUtil.spaPath('src', 'assets', '**', '*.*') 21 | ); 22 | 23 | // Use auto-generated en-US file even if site has no file. 24 | // This will let sites use consumed library files without having to create their own en_US file. 25 | const hasLocaleFile = filePaths.some(filePath => localeAssetsProcessor.isLocaleFile(filePath)); 26 | if (!hasLocaleFile) { 27 | filePaths = filePaths.concat( 28 | localeAssetsProcessor.getDefaultLocaleFiles('.skypageslocales') 29 | ); 30 | } 31 | 32 | const pathMap = filePaths.map(filePath => { 33 | let key; 34 | let location; 35 | 36 | if (localeAssetsProcessor.isLocaleFile(filePath)) { 37 | const basename = localeAssetsProcessor.parseLocaleFileBasename(filePath); 38 | key = ['locales', basename].join('/'); 39 | location = ['~', 'assets', basename].join('/'); 40 | } else { 41 | key = filePath.substr(assetsPath.length + 1); 42 | location = '~' + filePath.substr(srcPath.length); 43 | } 44 | 45 | return `'${key}': '${location}'`; 46 | }); 47 | 48 | const src = 49 | `export class ${getClassName()} { 50 | public getUrl(filePath: string): string { 51 | const pathMap: {[key: string]: any} = { 52 | ${pathMap.join(',\n' + codegen.indent(3))} 53 | }; 54 | 55 | return pathMap[filePath]; 56 | } 57 | }`; 58 | 59 | return src; 60 | } 61 | 62 | module.exports = { 63 | getSource: getSource, 64 | getClassName: getClassName 65 | }; 66 | -------------------------------------------------------------------------------- /lib/sky-pages-component-generator.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const fs = require('fs'); 5 | const glob = require('glob'); 6 | const path = require('path'); 7 | 8 | function generateImport(component) { 9 | const definition = 10 | `// BEGIN IMPORTED COMPONENT: ${component.componentName} 11 | import { ${component.componentName} } from '${component.importPath}'; 12 | // END IMPORTED COMPONENT: ${component.componentName}`; 13 | return definition; 14 | } 15 | 16 | function extractComponentName(file) { 17 | const content = fs.readFileSync(file, { encoding: 'utf8' }); 18 | const matches = content.split(/@Component\s*\([\s\S]+?\)\s*export\s+class\s+(\w+)/g); 19 | 20 | switch (matches.length) { 21 | case 3: 22 | return matches[1]; 23 | 24 | case 1: 25 | case 2: 26 | throw new Error(`Unable to locate an exported class in ${file}`); 27 | 28 | default: 29 | throw new Error(`As a best practice, please export one component per file in ${file}`); 30 | } 31 | } 32 | 33 | function generateImports(components) { 34 | return components 35 | .map(component => generateImport(component)) 36 | .join('\n\n'); 37 | } 38 | 39 | function generateNames(components) { 40 | return components.map(component => component.componentName); 41 | } 42 | 43 | function generateComponents(skyAppConfig) { 44 | // Prepend the alias and remove the file extension, 45 | // since the file extension causes a TypeScript error. 46 | return glob 47 | .sync(path.join(skyAppConfig.runtime.srcPath, skyAppConfig.runtime.componentsPattern), { 48 | ignore: [ 49 | path.join(skyAppConfig.runtime.srcPath, skyAppConfig.runtime.componentsIgnorePattern) 50 | ] 51 | }) 52 | .map(file => ({ 53 | importPath: skyAppConfig.runtime.spaPathAlias + '/' + file.replace(/\.[^\.]+$/, ''), 54 | componentName: extractComponentName(file) 55 | })); 56 | } 57 | 58 | function getComponents(skyAppConfig) { 59 | const components = skyAppConfig.runtime.components || generateComponents(skyAppConfig); 60 | return { 61 | imports: generateImports(components), 62 | names: generateNames(components) 63 | }; 64 | } 65 | 66 | module.exports = { 67 | getComponents: getComponents 68 | }; 69 | -------------------------------------------------------------------------------- /loader/sky-app-config/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | module.exports = function (source) { 5 | 6 | // Legacy code called this skyPagesConfig, so we'll leave it for now. 7 | const skyPagesConfig = this.options.skyPagesConfig; 8 | const runtime = JSON.stringify(skyPagesConfig.runtime); 9 | const skyux = JSON.stringify(skyPagesConfig.skyux); 10 | 11 | const runtimeDeclaration = `public static runtime: RuntimeConfig;`; 12 | const skyuxDeclaration = `public static skyux: SkyuxConfig;`; 13 | 14 | source = source.replace(runtimeDeclaration, `${runtimeDeclaration.slice(0, -1)} = ${runtime};`); 15 | source = source.replace(skyuxDeclaration, `${skyuxDeclaration.slice(0, -1)} = ${skyux};`); 16 | 17 | return source; 18 | }; 19 | -------------------------------------------------------------------------------- /loader/sky-assets/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const loaderUtils = require('loader-utils'); 6 | 7 | const assetsProcessor = require('../../lib/assets-processor'); 8 | 9 | module.exports = function (content) { 10 | const options = loaderUtils.getOptions(this); 11 | 12 | content = assetsProcessor.processAssets( 13 | content, 14 | options && options.baseUrl, 15 | (filePathWithHash, physicalFilePath) => { 16 | this.emitFile( 17 | filePathWithHash, 18 | fs.readFileSync(physicalFilePath) 19 | ); 20 | } 21 | ); 22 | 23 | return content; 24 | }; 25 | -------------------------------------------------------------------------------- /loader/sky-pages-module/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const generator = require('../../lib/sky-pages-module-generator'); 7 | 8 | module.exports = function () { 9 | 10 | // Hacky way to trigger rebuild of sky-pages.module.ts 11 | // Would love to find a better webpack way to handle this. 12 | const moduleFilename = 'sky-pages.module'; 13 | const moduleResolved = path.join(__dirname, '..', '..', 'src', 'app', moduleFilename + '.ts'); 14 | const template = fs.readFileSync(moduleResolved, { encoding: 'utf8' }); 15 | const regex = /\/\/ TS \((.*)\)/; 16 | 17 | function writeTimeStamp() { 18 | const ts = +new Date(); 19 | fs.writeFileSync(moduleResolved, template.replace(regex, `// TS (${ts}`)); 20 | } 21 | 22 | this._compiler.plugin('invalid', function (filename) { 23 | const filenameParsed = path.parse(filename); 24 | switch (filenameParsed.ext) { 25 | case '.html': 26 | writeTimeStamp(); 27 | break; 28 | 29 | case '.ts': 30 | if (filenameParsed.name !== 'sky-pages.module') { 31 | writeTimeStamp(); 32 | } 33 | 34 | break; 35 | } 36 | }); 37 | 38 | return generator.getSource(this.options.skyPagesConfig); 39 | }; 40 | -------------------------------------------------------------------------------- /loader/sky-processor/index.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const logger = require('@blackbaud/skyux-logger'); 5 | let plugins; 6 | 7 | const getPluginContents = (skyPagesConfig) => { 8 | let contents = []; 9 | 10 | if (skyPagesConfig && 11 | skyPagesConfig.skyux && 12 | skyPagesConfig.skyux.plugins && 13 | skyPagesConfig.skyux.plugins.length 14 | ) { 15 | skyPagesConfig.skyux.plugins.forEach(path => { 16 | try { 17 | contents.push(require(path)); 18 | } catch (error) { 19 | logger.info(`Plugin not found: ${path}`); 20 | } 21 | }); 22 | } 23 | 24 | return contents; 25 | }; 26 | 27 | const processContent = (content, callbackName, ...additionalArgs) => { 28 | plugins.forEach(plugin => { 29 | let callback = plugin[callbackName]; 30 | if (typeof callback === 'function') { 31 | content = callback.call({}, content, ...additionalArgs) || content; 32 | } 33 | }); 34 | 35 | return content; 36 | }; 37 | 38 | function preload(content, loaderConfig) { 39 | const skyPagesConfig = loaderConfig.options.skyPagesConfig; 40 | plugins = getPluginContents(skyPagesConfig); 41 | return processContent( 42 | content, 43 | 'preload', 44 | loaderConfig.resourcePath, 45 | skyPagesConfig 46 | ); 47 | } 48 | 49 | function postload(content, loaderConfig) { 50 | const skyPagesConfig = loaderConfig.options.skyPagesConfig; 51 | plugins = getPluginContents(skyPagesConfig); 52 | return processContent( 53 | content, 54 | 'postload', 55 | loaderConfig.resourcePath, 56 | skyPagesConfig 57 | ); 58 | } 59 | 60 | module.exports = { 61 | preload, 62 | postload 63 | }; 64 | -------------------------------------------------------------------------------- /loader/sky-processor/postload.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const processor = require('./index'); 5 | 6 | module.exports = function (content) { 7 | return processor.postload.call(this, content, this); 8 | }; 9 | -------------------------------------------------------------------------------- /loader/sky-processor/preload.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const processor = require('./index'); 5 | 6 | module.exports = function (content) { 7 | return processor.preload.call(this, content, this); 8 | }; 9 | -------------------------------------------------------------------------------- /plugin/output-keep-alive/index.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | /** 5 | * For longer builds, this plugin periodically prints to the 6 | * console to reset any timeouts associated with watched output. 7 | * More info: 8 | * https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received 9 | * 10 | * @name OutputKeepAlivePlugin 11 | * @param {any} options 12 | */ 13 | function OutputKeepAlivePlugin(options = {}) { 14 | this.apply = function (compiler) { 15 | if (!options.enabled) { 16 | return; 17 | } 18 | 19 | compiler.plugin('compilation', function (compilation) { 20 | // More hooks found on the docs: 21 | // https://webpack.js.org/api/compilation/ 22 | compilation.plugin('build-module', function () { 23 | process.stdout.write('.'); 24 | }); 25 | }); 26 | }; 27 | } 28 | 29 | module.exports = { OutputKeepAlivePlugin }; 30 | -------------------------------------------------------------------------------- /plugin/save-metadata/index.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const util = require('util'); 7 | const hostUtils = require('../../utils/host-utils'); 8 | 9 | module.exports = function SaveMetadata() { 10 | 11 | function getFallbackName(name) { 12 | return util.format('SKY_PAGES_READY_%s', name.toUpperCase().replace(/\./g, '_')); 13 | } 14 | 15 | this.plugin('emit', (compilation, done) => { 16 | 17 | // Add our fallback variable to the bottom of the JS source files 18 | Object.keys(compilation.assets).forEach((key) => { 19 | const parsed = path.parse(key); 20 | if (parsed.ext === '.js') { 21 | const asset = compilation.assets[key]; 22 | const source = asset.source(); 23 | asset.source = () => util.format( 24 | '%s\nvar %s = true;\n', 25 | source, 26 | getFallbackName(parsed.name) 27 | ); 28 | } 29 | }); 30 | 31 | done(); 32 | }); 33 | 34 | this.plugin('done', (stats) => { 35 | 36 | let metadata = []; 37 | hostUtils.getScripts(stats.toJson().chunks).forEach(script => { 38 | metadata.push({ 39 | name: script.name, 40 | fallback: getFallbackName(path.parse(script.name).name) 41 | }); 42 | }); 43 | 44 | fs.writeFileSync( 45 | path.join(process.cwd(), 'dist', 'metadata.json'), 46 | JSON.stringify(metadata, null, '\t') 47 | ); 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /runtime/assets.service.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppAssetsService 3 | } from '@skyux/assets/assets.service'; 4 | -------------------------------------------------------------------------------- /runtime/auth-http.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAuthHttp 3 | } from '@skyux/http/modules/auth-http/auth-http'; 4 | -------------------------------------------------------------------------------- /runtime/auth-token-provider.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAuthTokenProvider 3 | } from '@skyux/http/modules/auth-http/auth-token-provider'; 4 | -------------------------------------------------------------------------------- /runtime/bootstrapper.spec.ts: -------------------------------------------------------------------------------- 1 | import { SkyAppBootstrapper } from './bootstrapper'; 2 | 3 | import { 4 | BBAuth, 5 | BBContextProvider 6 | } from '@blackbaud/auth-client'; 7 | 8 | describe('bootstrapper', () => { 9 | 10 | let getTokenSpy: jasmine.Spy; 11 | let ensureContext: jasmine.Spy; 12 | let historyReplaceStateSpy: jasmine.Spy; 13 | let getUrlSpy: jasmine.Spy; 14 | 15 | function validateContextProvided( 16 | testEnvId: string, 17 | testUrl: string, 18 | expectedUrl: string 19 | ): Promise { 20 | let contextPromiseResolve: any; 21 | 22 | const contextPromise = new Promise((resolve) => { 23 | contextPromiseResolve = resolve; 24 | }); 25 | 26 | return new Promise((resolve) => { 27 | getTokenSpy.and.returnValue(Promise.resolve()); 28 | historyReplaceStateSpy.and.stub(); 29 | 30 | getUrlSpy.and.returnValue(testUrl); 31 | 32 | ensureContext.and.returnValue(contextPromise); 33 | 34 | SkyAppBootstrapper.config = { 35 | auth: true, 36 | params: [] 37 | }; 38 | 39 | SkyAppBootstrapper.processBootstrapConfig().then(() => { 40 | if (testUrl === expectedUrl) { 41 | expect(historyReplaceStateSpy).not.toHaveBeenCalled(); 42 | } else { 43 | expect(historyReplaceStateSpy).toHaveBeenCalledWith( 44 | {}, 45 | '', 46 | expectedUrl 47 | ); 48 | } 49 | 50 | resolve(); 51 | }); 52 | 53 | contextPromiseResolve({ 54 | envId: testEnvId, 55 | svcId: 'abc', 56 | url: 'https://example.com?envid=123' 57 | }); 58 | }); 59 | } 60 | 61 | beforeEach(() => { 62 | getTokenSpy = spyOn(BBAuth, 'getToken'); 63 | ensureContext = spyOn(BBContextProvider, 'ensureContext'); 64 | historyReplaceStateSpy = spyOn(history, 'replaceState').and.callThrough(); 65 | getUrlSpy = spyOn(SkyAppBootstrapper as any, 'getUrl').and.callThrough(); 66 | }); 67 | 68 | afterEach(() => { 69 | getTokenSpy.and.stub(); 70 | ensureContext.and.stub(); 71 | historyReplaceStateSpy.and.callThrough(); 72 | getUrlSpy.and.callThrough(); 73 | }); 74 | 75 | it('should immediately resolve if SkyAppConfig.config.skyux.auth is not set', (done) => { 76 | SkyAppBootstrapper.config = { 77 | params: [] 78 | }; 79 | 80 | SkyAppBootstrapper.processBootstrapConfig().then(done); 81 | }); 82 | 83 | it('should call if BBAuth.getToken if SkyAppConfig.config.skyux.auth is set', (done) => { 84 | getTokenSpy.and.returnValue(Promise.resolve()); 85 | ensureContext.and.returnValue(Promise.resolve({})); 86 | 87 | SkyAppBootstrapper.config = { 88 | auth: true, 89 | params: [] 90 | }; 91 | 92 | SkyAppBootstrapper.processBootstrapConfig().then(() => { 93 | expect(getTokenSpy).toHaveBeenCalled(); 94 | done(); 95 | }); 96 | }); 97 | 98 | it('should wait for context from BBContextProvider to provide the required context', (done) => { 99 | validateContextProvided( 100 | '123', 101 | 'https://example.com', 102 | 'https://example.com?envid=123' 103 | ) 104 | .then(done); 105 | }); 106 | 107 | it('should not replace state when the resolved URL matches the current URL', (done) => { 108 | validateContextProvided( 109 | '123', 110 | 'https://example.com?envid=123', 111 | 'https://example.com?envid=123' 112 | ) 113 | .then(done); 114 | }); 115 | 116 | }); 117 | -------------------------------------------------------------------------------- /runtime/bootstrapper.ts: -------------------------------------------------------------------------------- 1 | //#region imports 2 | 3 | import { 4 | BBAuth, 5 | BBContextArgs, 6 | BBContextProvider 7 | } from '@blackbaud/auth-client'; 8 | 9 | import { 10 | SkyuxConfig 11 | } from './config'; 12 | 13 | import { 14 | SkyAppRuntimeConfigParams 15 | } from './params'; 16 | 17 | //#endregion 18 | 19 | export class SkyAppBootstrapper { 20 | 21 | public static config: SkyuxConfig; 22 | 23 | public static processBootstrapConfig(): Promise { 24 | if (SkyAppBootstrapper.config && SkyAppBootstrapper.config.auth) { 25 | return BBAuth.getToken() 26 | .then(() => { 27 | const currentUrl = this.getUrl(); 28 | 29 | const params = new SkyAppRuntimeConfigParams( 30 | currentUrl, 31 | this.config.params! 32 | ); 33 | 34 | const ensureContextArgs: BBContextArgs = { 35 | envId: params.get('envid'), 36 | envIdRequired: params.isRequired('envid'), 37 | leId: params.get('leid'), 38 | leIdRequired: params.isRequired('leid'), 39 | svcId: params.get('svcid'), 40 | svcIdRequired: params.isRequired('svcid'), 41 | url: currentUrl 42 | }; 43 | 44 | return BBContextProvider.ensureContext(ensureContextArgs) 45 | .then((args) => { 46 | // The URL will remain the same if the required context is already present, in which 47 | // case there's no need to update the URL. 48 | if (args.url !== currentUrl) { 49 | history.replaceState( 50 | {}, 51 | '', 52 | args.url 53 | ); 54 | } 55 | }); 56 | }); 57 | } else { 58 | return Promise.resolve(); 59 | } 60 | } 61 | 62 | private static getUrl(): string { 63 | return window.location.href; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /runtime/config-params.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyuxConfigParams 3 | } from '@skyux/config/config-params'; 4 | -------------------------------------------------------------------------------- /runtime/config.ts: -------------------------------------------------------------------------------- 1 | export { 2 | RuntimeConfigApp, 3 | RuntimeConfig, 4 | SkyAppConfig, 5 | SkyuxConfig, 6 | SkyuxConfigA11y, 7 | SkyuxConfigApp, 8 | SkyuxConfigHost, 9 | SkyuxConfigTestSettings, 10 | SkyuxConfigUnitTestSettings, 11 | SkyuxPactConfig 12 | } from '@skyux/config'; 13 | -------------------------------------------------------------------------------- /runtime/directives/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sky-app-link.directive'; 2 | export * from './sky-app-link-external.directive'; 3 | -------------------------------------------------------------------------------- /runtime/directives/sky-app-link-external.directive.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppLinkExternalDirective 3 | } from '@skyux/router/modules/link/link-external.directive'; 4 | -------------------------------------------------------------------------------- /runtime/directives/sky-app-link.directive.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppLinkDirective 3 | } from '@skyux/router/modules/link/link.directive'; 4 | -------------------------------------------------------------------------------- /runtime/format.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppFormat 3 | } from '@skyux/core/modules/format'; 4 | -------------------------------------------------------------------------------- /runtime/i18n/host-locale-provider.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SkyAppHostLocaleProvider 3 | } from './host-locale-provider'; 4 | 5 | describe('Host locale provider', () => { 6 | 7 | const mockWindowRef: any = { 8 | nativeWindow: { 9 | SKYUX_HOST: { 10 | acceptLanguage: 'en-GB' 11 | } 12 | } 13 | }; 14 | 15 | it('should get locale info from the global SKYUX_HOST variable', (done) => { 16 | const localeProvider = new SkyAppHostLocaleProvider(mockWindowRef); 17 | 18 | localeProvider.getLocaleInfo().subscribe((info: any) => { 19 | expect(info.locale).toBe('en-GB'); 20 | done(); 21 | }); 22 | }); 23 | 24 | it( 25 | 'should fall back to default local if the global SKYUX_HOST variable does not ' + 26 | 'specify a language', 27 | (done) => { 28 | mockWindowRef.nativeWindow.SKYUX_HOST.acceptLanguage = undefined; 29 | 30 | const localeProvider = new SkyAppHostLocaleProvider(mockWindowRef); 31 | 32 | localeProvider.getLocaleInfo().subscribe((info: any) => { 33 | expect(info.locale).toBe('en-US'); 34 | done(); 35 | }); 36 | } 37 | ); 38 | }); 39 | -------------------------------------------------------------------------------- /runtime/i18n/host-locale-provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable 3 | } from '@angular/core'; 4 | 5 | import { 6 | SkyAppWindowRef 7 | } from '../window-ref'; 8 | 9 | import { 10 | Observable 11 | } from 'rxjs/Observable'; 12 | 13 | import { 14 | SkyAppLocaleInfo 15 | } from './locale-info'; 16 | 17 | import { 18 | SkyAppLocaleProvider 19 | } from './locale-provider'; 20 | 21 | @Injectable() 22 | export class SkyAppHostLocaleProvider extends SkyAppLocaleProvider { 23 | constructor( 24 | private windowRef: SkyAppWindowRef 25 | ) { 26 | super(); 27 | } 28 | 29 | public getLocaleInfo(): Observable { 30 | let locale: string | undefined; 31 | 32 | const skyuxHost = (this.windowRef.nativeWindow as any).SKYUX_HOST; 33 | 34 | if (skyuxHost) { 35 | const acceptLanguage = skyuxHost.acceptLanguage || ''; 36 | locale = acceptLanguage.split(',')[0]; 37 | } 38 | 39 | locale = locale || this.defaultLocale; 40 | 41 | return Observable.of({ 42 | locale: locale 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /runtime/i18n/index.ts: -------------------------------------------------------------------------------- 1 | export * from './host-locale-provider'; 2 | export * from './locale-provider'; 3 | export * from './resources.pipe'; 4 | export * from './resources.service'; 5 | -------------------------------------------------------------------------------- /runtime/i18n/locale-info.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppLocaleInfo 3 | } from '@skyux/i18n/modules/i18n/locale-info'; 4 | -------------------------------------------------------------------------------- /runtime/i18n/locale-provider.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppLocaleProvider 3 | } from '@skyux/i18n/modules/i18n/locale-provider'; 4 | -------------------------------------------------------------------------------- /runtime/i18n/resources.pipe.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppResourcesPipe 3 | } from '@skyux/i18n/modules/i18n/resources.pipe'; 4 | -------------------------------------------------------------------------------- /runtime/i18n/resources.service.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppResourcesService 3 | } from '@skyux/i18n/modules/i18n/resources.service'; 4 | -------------------------------------------------------------------------------- /runtime/i18n/resources.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppResources 3 | } from '@skyux/i18n/modules/i18n/resources'; 4 | -------------------------------------------------------------------------------- /runtime/index.ts: -------------------------------------------------------------------------------- 1 | export * from './assets.service'; 2 | export * from './auth-http'; 3 | export * from './auth-token-provider'; 4 | export * from './pact-auth-token-provider'; 5 | export * from './pact.service'; 6 | export * from './bootstrapper'; 7 | export * from './config'; 8 | export * from './params'; 9 | export * from './directives'; 10 | export * from './search-results-provider'; 11 | export * from './window-ref'; 12 | export * from './style-loader'; 13 | export * from './viewport.service'; 14 | export * from './omnibar-provider'; 15 | export * from './omnibar-ready-args'; 16 | export * from './runtime.module'; 17 | -------------------------------------------------------------------------------- /runtime/omnibar-provider.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppOmnibarProvider 3 | } from '@skyux/omnibar-interop/omnibar-provider'; 4 | -------------------------------------------------------------------------------- /runtime/omnibar-ready-args.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppOmnibarReadyArgs 3 | } from '@skyux/omnibar-interop/omnibar-ready-args'; 4 | -------------------------------------------------------------------------------- /runtime/pact-auth-token-provider.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyPactAuthTokenProvider 3 | } from '@skyux-sdk/pact/modules/pact/pact-auth-token-provider'; 4 | -------------------------------------------------------------------------------- /runtime/pact.service.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyPactService 3 | } from '@skyux-sdk/pact/modules/pact/pact.service'; 4 | -------------------------------------------------------------------------------- /runtime/params.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppRuntimeConfigParams 3 | } from '@skyux/config/params'; 4 | -------------------------------------------------------------------------------- /runtime/runtime.module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NgModule 3 | } from '@angular/core'; 4 | 5 | import { 6 | SkyAppLinkModule 7 | } from '@skyux/router/modules/link/link.module'; 8 | 9 | import { 10 | SkyI18nModule 11 | } from '@skyux/i18n/modules/i18n/i18n.module'; 12 | 13 | @NgModule({ 14 | imports: [ 15 | SkyAppLinkModule, 16 | SkyI18nModule 17 | ], 18 | exports: [ 19 | SkyAppLinkModule, 20 | SkyI18nModule 21 | ] 22 | }) 23 | export class SkyAppRuntimeModule { } 24 | -------------------------------------------------------------------------------- /runtime/search-results-provider.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppSearchResultsProvider 3 | } from '@skyux/omnibar-interop/search-results-provider'; 4 | -------------------------------------------------------------------------------- /runtime/style-loader.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppStyleLoader 3 | } from '@skyux/theme/style-loader'; 4 | -------------------------------------------------------------------------------- /runtime/testing/browser/i18n/resources-test.service.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppResourcesTestService 3 | } from '@skyux/i18n/testing/resources-test.service'; 4 | -------------------------------------------------------------------------------- /runtime/testing/browser/index.ts: -------------------------------------------------------------------------------- 1 | export * from './matchers'; 2 | export * from './test-module'; 3 | export * from './test-utility'; 4 | -------------------------------------------------------------------------------- /runtime/testing/browser/matchers.ts: -------------------------------------------------------------------------------- 1 | export { 2 | expect 3 | } from '@skyux-sdk/testing/matchers/matchers'; 4 | -------------------------------------------------------------------------------- /runtime/testing/browser/test-module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { APP_BASE_HREF } from '@angular/common'; 4 | 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | 7 | import { SkyPagesModule } from '../../../src/app/sky-pages.module'; 8 | 9 | import { SkyAppResourcesService } from '../../../runtime/i18n'; 10 | import { SkyAppResourcesTestService } from './i18n/resources-test.service'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | RouterTestingModule, 15 | SkyPagesModule 16 | ], 17 | providers: [ 18 | { 19 | provide: APP_BASE_HREF, 20 | useValue : '/' 21 | }, 22 | { 23 | provide: SkyAppResourcesService, 24 | useClass: SkyAppResourcesTestService 25 | } 26 | ] 27 | }) 28 | export class SkyAppTestModule { } 29 | -------------------------------------------------------------------------------- /runtime/testing/browser/test-utility-dom-event-options.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppTestUtilityDomEventOptions 3 | } from '@skyux-sdk/testing/test-utility/test-utility-dom-event-options'; 4 | -------------------------------------------------------------------------------- /runtime/testing/browser/test-utility.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppTestUtility 3 | } from '@skyux-sdk/testing/test-utility/test-utility'; 4 | -------------------------------------------------------------------------------- /runtime/testing/e2e/a11y.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyA11y 3 | } from '@skyux-sdk/e2e/a11y'; 4 | -------------------------------------------------------------------------------- /runtime/testing/e2e/host-browser.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyHostBrowser 3 | } from '@skyux-sdk/e2e/host-browser'; 4 | -------------------------------------------------------------------------------- /runtime/testing/e2e/index.ts: -------------------------------------------------------------------------------- 1 | export * from './host-browser'; 2 | export * from './a11y'; 3 | -------------------------------------------------------------------------------- /runtime/viewport.service.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppViewportService 3 | } from '@skyux/theme/viewport.service'; 4 | -------------------------------------------------------------------------------- /runtime/window-ref.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SkyAppWindowRef 3 | } from '@skyux/core/modules/window/window-ref'; 4 | -------------------------------------------------------------------------------- /skyuxconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@skyux/config/skyuxconfig-schema.json", 3 | "mode": "advanced", 4 | "host": { 5 | "url": "https://host.nxt.blackbaud.com" 6 | }, 7 | "app": { 8 | "title": "Blackbaud - SKY UX Application" 9 | }, 10 | "compileMode": "jit", 11 | "params": { 12 | "addin": true, 13 | "envid": true, 14 | "leid": true, 15 | "svcid": true 16 | }, 17 | "skyuxModules": [ 18 | "SkyModule" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/app/app-extras.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | @NgModule({}) 4 | export class AppExtrasModule {} 5 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .skyux-app-loading { 2 | visibility: hidden; 3 | } 4 | 5 | .sky-modal-body-full-page { 6 | // Hide the bb-Help-invoker when a full-page modal is present and the widget is closed. 7 | #bb-help-container.bb-help-closed > #bb-help-invoker { 8 | display: none; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | // This is only visible in EASY MODE 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule } from '@angular/router'; 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | import { AppComponent } from './app.component'; 6 | 7 | // File is dynamically built using webpack loader 8 | import { SkyPagesModule } from './sky-pages.module'; 9 | 10 | @NgModule({ 11 | declarations: [ AppComponent ], 12 | imports: [ 13 | BrowserModule, 14 | RouterModule, 15 | SkyPagesModule 16 | ], 17 | bootstrap: [ AppComponent ] 18 | }) 19 | export class AppModule { } 20 | -------------------------------------------------------------------------------- /src/app/sky-pages.module.ts: -------------------------------------------------------------------------------- 1 | // File dynamically generated via webpack. 2 | // Matching definition makes IDE's happy. 3 | export class SkyPagesModule {} 4 | 5 | // The following comment is used to track timestamps in `skyux serve` 6 | // TS (1492006796510 END 7 | -------------------------------------------------------------------------------- /src/assets/locales/resources_en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "skyux_builder_page_not_found_iframe_title": { 3 | "message": "Page not found", 4 | "_description": "A string value to represent the Page Not Found iframe title attribute." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/main-internal.aot.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowser } from '@angular/platform-browser'; 2 | import { AppModuleNgFactory } from './ngfactory/app/app.module.ngfactory'; 3 | 4 | import { SkyAppBootstrapper } from '@blackbaud/skyux-builder/runtime/bootstrapper'; 5 | 6 | SkyAppBootstrapper.processBootstrapConfig().then(() => { 7 | platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); 8 | }); 9 | -------------------------------------------------------------------------------- /src/main-internal.ts: -------------------------------------------------------------------------------- 1 | declare var module: any; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { NgModuleRef } from '@angular/core'; 5 | import { AppModule } from './app/app.module'; 6 | import { SkyAppBootstrapper } from '../runtime/bootstrapper'; 7 | 8 | SkyAppBootstrapper 9 | .processBootstrapConfig() 10 | .then(() => platformBrowserDynamic().bootstrapModule(AppModule)) 11 | .then((ngModuleRef: NgModuleRef) => { 12 | if (module.hot) { 13 | module.hot.accept(); 14 | module.hot.dispose(() => ngModuleRef.destroy()); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /src/main.ejs: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | <%= htmlWebpackPlugin.options.skyux.app.title %> 13 | 14 | <%= getExternal('css', 'before') %> 15 | <%= getExternal('css', 'after') %> 16 | <%= getExternal('js', 'before', true) %> 17 | <%= getExternal('js', 'after', true) %> 18 | 19 | 20 | 21 | Loading... 22 | 23 | <%= getExternal('js', 'before', false) %> 24 | 25 | 26 | <% htmlWebpackPlugin.files.js.forEach(file => { %> 27 | 28 | <% }) %> 29 | 30 | <%= getExternal('js', 'after', false) %> 31 | 32 | 33 | 34 | 35 | <% 36 | function getExternal(type, hook, isHead) { 37 | if (htmlWebpackPlugin.options.skyux.app.externals 38 | && htmlWebpackPlugin.options.skyux.app.externals[type] 39 | && htmlWebpackPlugin.options.skyux.app.externals[type][hook]) { 40 | 41 | function getIntegrity(entry) { 42 | return entry.integrity ? ` integrity="${entry.integrity}" crossorigin="anonymous"` : ''; 43 | } 44 | 45 | var html = ``; 46 | htmlWebpackPlugin.options.skyux.app.externals[type][hook].forEach(entry => { 47 | switch (type) { 48 | case 'css': 49 | html += ``; 50 | break; 51 | case 'js': 52 | if ((isHead && entry.head) || (!isHead && !entry.head)) { 53 | html += ``; 54 | } 55 | break; 56 | } 57 | }); 58 | return html; 59 | } 60 | } 61 | %> 62 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | // This can be overridden by the SPA to bootstrap things like the omnibar or auth. 2 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | import 'core-js/es6'; 2 | import 'core-js/es7/reflect'; 3 | import 'zone.js/dist/zone'; 4 | import 'ts-helpers'; 5 | 6 | import 'web-animations-js/web-animations.min'; 7 | 8 | if (process.env.ENV === 'production') { 9 | // Production 10 | } else { 11 | // Development 12 | Error['stackTraceLimit'] = Infinity; 13 | require('zone.js/dist/long-stack-trace-zone'); 14 | } 15 | -------------------------------------------------------------------------------- /src/skyux.ts: -------------------------------------------------------------------------------- 1 | import '@skyux/theme/css/sky.css'; 2 | import '@blackbaud/skyux/dist/core'; 3 | -------------------------------------------------------------------------------- /src/vendor.ts: -------------------------------------------------------------------------------- 1 | // Angular 2 2 | import '@angular/platform-browser-dynamic'; 3 | import '@angular/core'; 4 | import '@angular/common'; 5 | import '@angular/http'; 6 | import '@angular/router'; 7 | 8 | // RxJS 9 | import 'rxjs'; 10 | // Other vendors for example jQuery, Lodash or Bootstrap 11 | // You can import js, ts, css, sass, ... 12 | -------------------------------------------------------------------------------- /ssl/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEIzCCAwugAwIBAgIJAKgB1PJS/92LMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYD 3 | VQQGEwJVUzELMAkGA1UECBMCU0MxEzARBgNVBAcTCkNoYXJsZXN0b24xEjAQBgNV 4 | BAoTCUJsYWNrYmF1ZDEPMA0GA1UECxMGU0tZIFVYMQ8wDQYDVQQDEwZTS1kgVVgx 5 | KzApBgkqhkiG9w0BCQEWHHNreS1idWlsZC11c2VyQGJsYWNrYmF1ZC5jb20wIBcN 6 | MTgwOTA3MDAyNTQ3WhgPMjExODA4MTQwMDI1NDdaMIG5MQswCQYDVQQGEwJVUzEX 7 | MBUGA1UECAwOU291dGggQ2Fyb2xpbmExEzARBgNVBAcMCkNoYXJsZXN0b24xEjAQ 8 | BgNVBAoMCUJsYWNrYmF1ZDErMCkGA1UECwwiUmVzZWFyY2gsIERlbGl2ZXJ5LCBh 9 | bmQgT3BlcmF0aW9uczErMCkGCSqGSIb3DQEJARYcc2t5LWJ1aWxkLXVzZXJAYmxh 10 | Y2tiYXVkLmNvbTEOMAwGA1UEAwwFU0tZVVgwggEiMA0GCSqGSIb3DQEBAQUAA4IB 11 | DwAwggEKAoIBAQDy2Wk7SVdtdjRxRzavQCJLI7sIokh4xjtWececb22xuRXzPS/C 12 | 535J3AmhqC7HrWu1h4B9g0ApGOE6sRVXNa/N7U3zjlPAML5PI8JSaMQJMLdhz2YA 13 | TWgGzrZvnaFwgx1CXfu9xLL+sx5ExbwNtLzVDJA6xvQHqZDD2Ee2Il7PHYGy7Xrn 14 | 12ZMhXNqw/WmjjxelfcgiFoY1mDPOhBNPrBWJlVRYI43Waw53JXNPpCw1eEmiscu 15 | LcVIo8vBEGCCcol6Cs/ZKU581NAhCgx4DZAzsAmbEVLsHhMWhj179HttM/JvR/rz 16 | qA8MDX8x7YsL1ekO+MSkEhKhgqKTFJ/1+IJhAgMBAAGjUTBPMB8GA1UdIwQYMBaA 17 | FGBmrfsEnQtAKF5vgP9l2pZLcZN4MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMBQG 18 | A1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAGLtdSq6MYIEM 19 | Mn9d2hERv01pgG0Mas1DtR8Md4askGGWe8Gz07oCn5BO8si/G7nFGcIY6WY52Wfm 20 | 7XBZJ2eEHUvDIushLXNhvGCTa6++ly6oj6FuTVoW5EAggoM1XljC/e2v/JPAb5ai 21 | Cw/wuV3oyufnnzoBsSvdwi5mqsvPfVS6YQhc1ZOy2C+m2RpmRCqMUP3wBevsEKkf 22 | KD6gYOtCTxByCEgsPTA2OvZBHSKlnk3ieaiI4DxDA0OnVIOh+uVeTKXMeS00X5mb 23 | iCo8pZY23RD/+9w9drzWVppxrfs08yRMaFne0S/wPNOWTZ+hMZ57XGsmEYvRY1XP 24 | HZseKOV7Fw== 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /ssl/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDy2Wk7SVdtdjRx 3 | RzavQCJLI7sIokh4xjtWececb22xuRXzPS/C535J3AmhqC7HrWu1h4B9g0ApGOE6 4 | sRVXNa/N7U3zjlPAML5PI8JSaMQJMLdhz2YATWgGzrZvnaFwgx1CXfu9xLL+sx5E 5 | xbwNtLzVDJA6xvQHqZDD2Ee2Il7PHYGy7Xrn12ZMhXNqw/WmjjxelfcgiFoY1mDP 6 | OhBNPrBWJlVRYI43Waw53JXNPpCw1eEmiscuLcVIo8vBEGCCcol6Cs/ZKU581NAh 7 | Cgx4DZAzsAmbEVLsHhMWhj179HttM/JvR/rzqA8MDX8x7YsL1ekO+MSkEhKhgqKT 8 | FJ/1+IJhAgMBAAECggEBALfDvNWYEghKwjRV5xOGPG0PhKBr7Ns3Zf9x95Jw31j0 9 | 7Z86VcHu2qmZT9B8K6n9mNusxZY0k4CFyylWhwePIJF7WNlMgiOUvu2z6X/itzUd 10 | ICdrgYwJBwbftT2Q0nEJRkLKS4y2I5yIfgcceckFUz8EWr+ffVmu/lS0fM9eAtBF 11 | 0k2ee1kqSEvXbALSHiEdVJ8xVn8UAVxoywBQYd6eVdo7in0mGtnIZYfC+5iQNdrA 12 | vdLgfmoZcOZ3VNR3U01ymooCn4GEGKWHv8m0XxcwrfdpvNueJ0PeGT4j1k88xh4/ 13 | 2NC2qG0FJRvZbkCUKuf45cv6vPNQh8fq4waAn0E66gECgYEA/b3Z022lFv57Zt0n 14 | uqP4KLR8F4Muv+kGE+cdHDZdlnTZoEmqS2KPKRjoFy4B0jiNLLyQNd5lTnxNTtJ5 15 | HRle75FcVJOHlZny+m+GZLS11JyrE3HoTbAd+y3WhLymDUKt1YdoIGFdCRF/LCaj 16 | lxGFQttrAzeHBbZsDEK01bMjy5kCgYEA9QK99T3KkHCbNWz0Or7h83iDkHzLidS+ 17 | If5QO6mGGctnsOWY+0OdixkltArBRjKfjKBVlFqvq54VgsbM6Me9dK0v8JNK8jK1 18 | CpZcViRPKF2eO+I83pHpZRzG6FjKlS0YRIxHytwP4AWMafQb2P1PYWqutKxcKZN7 19 | yncCY0xRagkCgYAdThznN0WW10NHSQl6m89gXB/s00DF91K1X77T8E90vgAYbAmX 20 | 9UUVeQPtEWoybkeXwBtjrVDD9MU08kf8nV6CiqZAOl2xYHtYgyLhZKGPcZysfT5Y 21 | IpwD03JwGB2RcH8FJ0NWYghNsNCgN8IzA1oBs7ezQml8tmnaLKYX/D2JGQKBgQC5 22 | MsNnlreBCr0nWx4ZMaQVp2i7VLl9i+PUSilXj8KfyNKuMj663tc9B1sqhl6lsypK 23 | 3/8QTqQu8yWLXr4Qzrp0cVylWpDyFkYmpJVTP8rd1jX/Sfl8u4pSNbhcdJFcxWNs 24 | nSS/QCx3x3nltPE/yemw1zULuKVJgAO4fNC/QjbgEQKBgQDwX8hFvpxcJ+mTEZGs 25 | bitXOZdwK0IMZ0bQkEBJUvOOkbnjeIpsZRssPBSF6k5wMfv9irF4jOhpABIrpxTB 26 | sr15N+FBk5F8Ztc2uFHG950d8i3z171/JdAUVzu462PLF8dD8XqqDVoXRgoe2kJE 27 | JovccFCjtapIiBYi/bHEF5a3cA== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /ssl/skyux-ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEpDCCA4ygAwIBAgIJAIdhnyzJXG6AMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYD 3 | VQQGEwJVUzELMAkGA1UECBMCU0MxEzARBgNVBAcTCkNoYXJsZXN0b24xEjAQBgNV 4 | BAoTCUJsYWNrYmF1ZDEPMA0GA1UECxMGU0tZIFVYMQ8wDQYDVQQDEwZTS1kgVVgx 5 | KzApBgkqhkiG9w0BCQEWHHNreS1idWlsZC11c2VyQGJsYWNrYmF1ZC5jb20wHhcN 6 | MTcwMTEwMTU0MTAzWhcNMTkxMDMxMTU0MTAzWjCBkjELMAkGA1UEBhMCVVMxCzAJ 7 | BgNVBAgTAlNDMRMwEQYDVQQHEwpDaGFybGVzdG9uMRIwEAYDVQQKEwlCbGFja2Jh 8 | dWQxDzANBgNVBAsTBlNLWSBVWDEPMA0GA1UEAxMGU0tZIFVYMSswKQYJKoZIhvcN 9 | AQkBFhxza3ktYnVpbGQtdXNlckBibGFja2JhdWQuY29tMIIBIjANBgkqhkiG9w0B 10 | AQEFAAOCAQ8AMIIBCgKCAQEA4jMK61vM9PMqNw06F5RGjieFOSz2asyHBEqatY2A 11 | /B1QS7id8TPGDP3kcRrvvvy69+y64alNF57JzACUXtn0PVUdre/a153pJjeOe+wa 12 | 02ig89vfmTMa4aNBQXQA2jgLB5Z88FvnmD7hQaAFPLooXsss+Hl1e1Pj6xER80I9 13 | oPQ5RDF7Ugt3dPhCTgOwuSFg+mv2Osi4ppjW/RB6h1P247rkMJi8MayEJRkClvjN 14 | 4cA9OjN7OkhCOtAw7ErhvOLSY5diFoK0yC/oLNdVoMNcOy739PpdyHkKvlkbwCmi 15 | awh50+n0oaob4e3evYSLAXKwRoftRMV49uWzooknQ8SciQIDAQABo4H6MIH3MB0G 16 | A1UdDgQWBBRgZq37BJ0LQCheb4D/ZdqWS3GTeDCBxwYDVR0jBIG/MIG8gBRgZq37 17 | BJ0LQCheb4D/ZdqWS3GTeKGBmKSBlTCBkjELMAkGA1UEBhMCVVMxCzAJBgNVBAgT 18 | AlNDMRMwEQYDVQQHEwpDaGFybGVzdG9uMRIwEAYDVQQKEwlCbGFja2JhdWQxDzAN 19 | BgNVBAsTBlNLWSBVWDEPMA0GA1UEAxMGU0tZIFVYMSswKQYJKoZIhvcNAQkBFhxz 20 | a3ktYnVpbGQtdXNlckBibGFja2JhdWQuY29tggkAh2GfLMlcboAwDAYDVR0TBAUw 21 | AwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANqJ71/My8+0x4UuBJZioZzHWA0vFgO4K 22 | /qjEKd71zQzJ6ZNztih3hYkw5uQCczfm/NTawwg2XwTNuNZIV23meIKrbJd5DG63 23 | sJsYYhfGR+ChvF+NXbQewzDnuQNfYgTMDQ2YleZFDDEDfnuB4EI0E/i9utjTdpB/ 24 | b062FGP7phn8dklnW1TenbQkOBA4lmG0nOGNq+VaI6JAGRHyRh6eaL8gD4NAIroH 25 | z25ZcpZhJ4juzfVt3AYxrk+ECT5NgYDIcvIBl/fZ7fs40l3yzsRC0wxOFPrPOyoq 26 | D0MwSYpi4yRsbP2biWS4tjjQDPIoFJUy919NrEN4rQIMLYhCxBx/9g== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /test/assets-utils.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | 6 | describe('Assets utilities', () => { 7 | 8 | beforeEach(() => { 9 | mock('hash-file', { 10 | sync: function () { 11 | return 'abcdefg'; 12 | } 13 | }); 14 | 15 | mock('../config/sky-pages/sky-pages.config', { 16 | spaPath: () => 'root' 17 | }); 18 | }); 19 | 20 | afterEach(() => { 21 | mock.stopAll(); 22 | }); 23 | 24 | it('should provide a method for appending a hash to the name of a file', () => { 25 | const assets = mock.reRequire('../utils/assets-utils'); 26 | 27 | const filePathWithHash = assets.getFilePathWithHash('/root/a/b/c.jpg'); 28 | 29 | expect(filePathWithHash).toBe('/a/b/c.abcdefg.jpg'); 30 | }); 31 | 32 | it('should provide a method for retrieving an asset URL', () => { 33 | const assets = mock.reRequire('../utils/assets-utils'); 34 | 35 | const filePathWithHash = assets.getUrl('https://example.com', '/root/a/b/c.jpg'); 36 | 37 | expect(filePathWithHash).toBe('https://example.com/a/b/c.abcdefg.jpg'); 38 | }); 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /test/cli-build.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | const logger = require('@blackbaud/skyux-logger'); 6 | 7 | describe('cli build', () => { 8 | 9 | it('should log when the build is completed successfully', (done) => { 10 | spyOn(logger, 'info'); 11 | mock('../cli/utils/run-build', () => Promise.resolve()); 12 | 13 | mock.reRequire('../cli/build')('build', {}, {}).then(() => { 14 | expect(logger.info).toHaveBeenCalledWith('Build successfully completed.'); 15 | done(); 16 | }); 17 | }); 18 | 19 | it('should return build stats when the build is completed successfully', (done) => { 20 | mock('../cli/utils/run-build', () => Promise.resolve({ foo: 'bar' })); 21 | 22 | mock.reRequire('../cli/build')('build', {}, {}).then((stats) => { 23 | expect(stats.foo).toEqual('bar'); 24 | done(); 25 | }); 26 | }); 27 | 28 | it('should log errors and set exit code to 1', (done) => { 29 | const errors = 'errors'; 30 | 31 | spyOn(logger, 'error'); 32 | spyOn(process, 'exit'); 33 | 34 | mock('../cli/utils/run-build', () => Promise.reject(errors)); 35 | 36 | mock.reRequire('../cli/build')('build', {}, {}).then(() => { 37 | expect(logger.error).toHaveBeenCalledWith(errors); 38 | expect(process.exit).toHaveBeenCalledWith(1); 39 | done(); 40 | }); 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /test/cli-lint.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | 6 | describe('cli lint', () => { 7 | afterEach(() => { 8 | mock.stopAll(); 9 | }); 10 | 11 | it('should run the linter', () => { 12 | spyOn(process, 'exit').and.returnValue(); 13 | mock('../cli/utils/ts-linter', { 14 | lintSync: () => { 15 | return { 16 | exitCode: 0 17 | }; 18 | } 19 | }); 20 | const lint = mock.reRequire('../cli/lint'); 21 | lint(); 22 | expect(process.exit).toHaveBeenCalledWith(0); 23 | }); 24 | 25 | it('should process the exit code', () => { 26 | spyOn(process, 'exit').and.returnValue(); 27 | mock('../cli/utils/ts-linter', { 28 | lintSync: () => { 29 | return { 30 | exitCode: 1 31 | }; 32 | } 33 | }); 34 | const lint = mock.reRequire('../cli/lint'); 35 | lint(); 36 | expect(process.exit).toHaveBeenCalledWith(1); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/cli-utils-config-resolver.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | const logger = require('@blackbaud/skyux-logger'); 6 | 7 | describe('utils/config-resolver.js', () => { 8 | 9 | afterEach(() => { 10 | mock.stopAll(); 11 | }); 12 | 13 | function setup(command, globResult, internal, argv) { 14 | mock('fs-extra', { 15 | existsSync: () => internal 16 | }); 17 | 18 | mock('glob', { 19 | sync: () => globResult 20 | }); 21 | 22 | const configResolver = mock.reRequire('../cli/utils/config-resolver'); 23 | return configResolver.resolve(command, argv); 24 | } 25 | 26 | function testCommand(command, argv) { 27 | let _filename; 28 | mock('path', { 29 | join: (root, dir, platform, filename) => _filename = filename 30 | }); 31 | 32 | setup(command, [], true, argv); 33 | return _filename; 34 | } 35 | 36 | it('should expose a resolve method', () => { 37 | const configResolver = require('../cli/utils/config-resolver'); 38 | expect(configResolver.resolve).toBeDefined(); 39 | }); 40 | 41 | it('should handle finding zero external configurations', () => { 42 | spyOn(logger, 'error'); 43 | spyOn(process, 'exit'); 44 | 45 | const config = setup('test', [], false, {}); 46 | expect(config).not.toBeDefined(); 47 | expect(logger.error).toHaveBeenCalledWith('Error locating a config file.'); 48 | expect(process.exit).toHaveBeenCalledWith(1); 49 | }); 50 | 51 | it('should handle finding one external configuration', () => { 52 | const result = 'external-file.js'; 53 | const config = setup('test', [result], false, {}); 54 | expect(config).toBe(result); 55 | }); 56 | 57 | it('should warn if multiple external config files found, but default to first', () => { 58 | spyOn(logger, 'warn'); 59 | spyOn(logger, 'info'); 60 | 61 | const results = [ 62 | 'one-too.js', 63 | 'many-files.js' 64 | ]; 65 | 66 | const config = setup('test', results, false, {}); 67 | expect(config).toBe(results[0]); 68 | expect(logger.warn).toHaveBeenCalledWith(`Found multiple external config files.`); 69 | }); 70 | 71 | it('should fallback to an internal config if it exists', () => { 72 | let resolveArgs = {}; 73 | 74 | mock('path', { 75 | join: (root, dir, platform, filename) => { 76 | resolveArgs = { 77 | root: root, 78 | dir: dir, 79 | platform: platform, 80 | filename: filename 81 | }; 82 | return 'resolved.js'; 83 | } 84 | }); 85 | 86 | const config = setup('test', [], true, {}); 87 | expect(config).toBe('resolved.js'); 88 | expect(resolveArgs.filename).toBe('config/karma/test.karma.conf.js'); 89 | }); 90 | 91 | it('should handle known karma commands', () => { 92 | ['test', 'watch'].forEach(command => { 93 | expect(testCommand(command, {})).toBe(`config/karma/${command}.karma.conf.js`); 94 | }); 95 | }); 96 | 97 | it('should handle the known protractor commands', () => { 98 | ['e2e', 'visual'].forEach(command => { 99 | expect(testCommand(command, {})).toBe(`config/protractor/protractor.conf.js`); 100 | }); 101 | }); 102 | 103 | it('should append the platform argument', () => { 104 | const custom = 'custom-platform'; 105 | 106 | let _platform; 107 | mock('path', { 108 | join: (root, dir, platform) => _platform = platform 109 | }); 110 | 111 | setup('test', [], true, { platform: custom }); 112 | expect(_platform).toBe(custom); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/cli-utils-prepare-library-package.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const mock = require('mock-require'); 6 | const logger = require('@blackbaud/skyux-logger'); 7 | 8 | describe('cli utils prepare-library-package', () => { 9 | let util; 10 | 11 | beforeEach(() => { 12 | mock('../config/sky-pages/sky-pages.config', { 13 | spaPath: (...args) => args.join('/') 14 | }); 15 | util = mock.reRequire('../cli/utils/prepare-library-package'); 16 | }); 17 | 18 | afterEach(() => { 19 | mock.stopAll(); 20 | }); 21 | 22 | it('should return a function', () => { 23 | expect(typeof util).toEqual('function'); 24 | }); 25 | 26 | it('should update the module property of package.json and write it to dist', () => { 27 | spyOn(fs, 'copySync').and.returnValue(); 28 | spyOn(fs, 'readJsonSync').and.returnValue({}); 29 | spyOn(fs, 'writeJsonSync').and.callFake((filePath, contents) => { 30 | expect(filePath.match('dist')).not.toEqual(null); 31 | expect(contents.module).toEqual('index.js'); 32 | }); 33 | spyOn(fs, 'existsSync').and.returnValue(true); 34 | util(); 35 | expect(fs.readJsonSync).toHaveBeenCalled(); 36 | expect(fs.writeJsonSync).toHaveBeenCalled(); 37 | }); 38 | 39 | it('should copy readme, changelog, and assets to dist', () => { 40 | spyOn(fs, 'readJsonSync').and.returnValue({}); 41 | spyOn(fs, 'writeJsonSync').and.returnValue(); 42 | spyOn(fs, 'copySync').and.returnValue(); 43 | spyOn(fs, 'existsSync').and.returnValue(true); 44 | util(); 45 | expect(fs.copySync).toHaveBeenCalledWith('README.md', 'dist/README.md'); 46 | expect(fs.copySync).toHaveBeenCalledWith('CHANGELOG.md', 'dist/CHANGELOG.md'); 47 | expect(fs.copySync).toHaveBeenCalledWith('src/assets', 'dist/src/assets'); 48 | }); 49 | 50 | it('should warn consumers if they do not include a readme, changelog, or assets', () => { 51 | const loggerSpy = spyOn(logger, 'warn'); 52 | 53 | spyOn(fs, 'readJsonSync').and.returnValue({}); 54 | spyOn(fs, 'writeJsonSync').and.returnValue(); 55 | spyOn(fs, 'copySync').and.callFake(() => {}); 56 | spyOn(fs, 'existsSync').and.returnValue(false); 57 | util(); 58 | expect(loggerSpy).toHaveBeenCalledWith('File(s) not found: README.md'); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/cli-utils-run-compiler.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | const logger = require('@blackbaud/skyux-logger'); 6 | 7 | describe('cli utils run compiler', () => { 8 | const requirePath = '../cli/utils/run-compiler'; 9 | let mockWebpack; 10 | 11 | beforeEach(() => { 12 | spyOn(logger, 'error'); 13 | spyOn(logger, 'warn'); 14 | spyOn(logger, 'info'); 15 | 16 | mockWebpack = () => { 17 | return { 18 | run: (cb) => cb(null, { 19 | toJson: () => ({ 20 | errors: [], 21 | warnings: [] 22 | }) 23 | }) 24 | }; 25 | }; 26 | }); 27 | 28 | it('should reject compilation errors', (done) => { 29 | const err = ['custom-error1']; 30 | mockWebpack = () => { 31 | return { 32 | run: (cb) => cb(err) 33 | }; 34 | }; 35 | 36 | const runCompiler = mock.reRequire(requirePath); 37 | runCompiler(mockWebpack, {}).catch(e => { 38 | expect(e).toBe(err); 39 | expect(logger.error).not.toHaveBeenCalled(); 40 | done(); 41 | }); 42 | }); 43 | 44 | it('should reject stats errors', (done) => { 45 | const errs = ['custom-error2']; 46 | 47 | mockWebpack = () => { 48 | return { 49 | run: (cb) => cb(null, { 50 | toJson: () => ({ 51 | errors: errs, 52 | warnings: [] 53 | }) 54 | }) 55 | }; 56 | }; 57 | 58 | const runCompiler = mock.reRequire(requirePath); 59 | 60 | runCompiler(mockWebpack, {}).catch((e) => { 61 | expect(e).toBe(errs); 62 | expect(logger.error).not.toHaveBeenCalled(); 63 | done(); 64 | }); 65 | }); 66 | 67 | it('should handle stats warnings', (done) => { 68 | const wrns = ['custom-warning1']; 69 | 70 | mockWebpack = () => { 71 | return { 72 | run: (cb) => cb(null, { 73 | toJson: () => ({ 74 | errors: [], 75 | warnings: wrns 76 | }) 77 | }) 78 | }; 79 | }; 80 | 81 | const runCompiler = mock.reRequire(requirePath); 82 | 83 | runCompiler(mockWebpack, {}).then(() => { 84 | expect(logger.warn).toHaveBeenCalledWith(wrns); 85 | done(); 86 | }); 87 | }); 88 | 89 | it('should handle no stats errors and warnings', (done) => { 90 | const runCompiler = mock.reRequire(requirePath); 91 | 92 | runCompiler(mockWebpack, {}).then(() => { 93 | expect(logger.error).not.toHaveBeenCalled(); 94 | expect(logger.warn).not.toHaveBeenCalled(); 95 | done(); 96 | }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /test/cli-utils-server.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const logger = require('@blackbaud/skyux-logger'); 5 | const mock = require('mock-require'); 6 | const path = require('path'); 7 | 8 | describe('server utils', () => { 9 | 10 | let closeCalled = false; 11 | let onErrorCB; 12 | let customServerError; 13 | let customPortError; 14 | let customPortNumber; 15 | 16 | beforeEach(() => { 17 | spyOn(logger, 'info'); 18 | }); 19 | 20 | afterEach(() => { 21 | closeCalled = false; 22 | onErrorCB = undefined; 23 | customServerError = undefined; 24 | customPortError = undefined; 25 | customPortNumber = undefined; 26 | mock.stopAll(); 27 | }); 28 | 29 | function bind() { 30 | mock('https', { 31 | createServer: () => ({ 32 | on: (err, cb) => onErrorCB = cb, 33 | close: () => closeCalled = true, 34 | listen: (port, host, cb) => { 35 | if (customServerError) { 36 | onErrorCB(customServerError); 37 | } 38 | cb(port); 39 | } 40 | }) 41 | }); 42 | 43 | mock('portfinder', { 44 | getPortPromise: () => { 45 | if (customPortError) { 46 | return Promise.reject(customPortError); 47 | } else { 48 | return Promise.resolve(customPortNumber); 49 | } 50 | } 51 | }); 52 | 53 | return mock.reRequire('../cli/utils/server'); 54 | } 55 | 56 | it('should expose start and stop methods', () => { 57 | const server = bind(); 58 | expect(server.start).toBeDefined(); 59 | expect(server.stop).toBeDefined(); 60 | }); 61 | 62 | it('should accept a root', () => { 63 | const server = bind(); 64 | const root = 'custom-root'; 65 | server.start(root).then(() =>{ 66 | 67 | }); 68 | }); 69 | 70 | it('should close the http server if it exists', (done) => { 71 | const server = bind(); 72 | 73 | server.stop(); 74 | expect(closeCalled).toBe(false); 75 | 76 | server.start().then(() => { 77 | logger.info.calls.reset(); 78 | server.stop(); 79 | expect(closeCalled).toBe(true); 80 | expect(logger.info).toHaveBeenCalledWith(`Stopping http server`); 81 | done(); 82 | }); 83 | }); 84 | 85 | it('should catch http server failures', (done) => { 86 | 87 | customServerError = 'custom-error'; 88 | const server = bind(); 89 | 90 | server.start().catch(err => { 91 | expect(err).toBe(customServerError); 92 | done(); 93 | }); 94 | }); 95 | 96 | it('should resolve the portfinder port', (done) => { 97 | customPortNumber = 1234; 98 | const server = bind(); 99 | 100 | server.start().then(port => { 101 | expect(port).toBe(customPortNumber); 102 | done(); 103 | }); 104 | }); 105 | 106 | it('should catch portfinder failures', (done) => { 107 | 108 | customPortError = 'custom-portfinder-error'; 109 | 110 | const server = bind(); 111 | server.start().catch(err => { 112 | expect(err).toBe(customPortError); 113 | done(); 114 | }); 115 | }); 116 | 117 | it('should allow the distPath to be specified', (done) => { 118 | spyOn(path, 'resolve').and.callThrough(); 119 | 120 | const customDistPath = 'custom-dist'; 121 | const server = bind(); 122 | 123 | server.start('custom-root', customDistPath) 124 | .then(() => { 125 | expect(path.resolve).toHaveBeenCalledWith(process.cwd(), customDistPath); 126 | done(); 127 | }); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/cli-utils-stage-library-ts.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const glob = require('glob'); 6 | const mock = require('mock-require'); 7 | const sass = require('node-sass'); 8 | 9 | describe('cli utils prepare-library-package', () => { 10 | let util; 11 | 12 | beforeEach(() => { 13 | spyOn(fs, 'copySync').and.returnValue(); 14 | spyOn(fs, 'removeSync').and.returnValue(); 15 | mock('../config/sky-pages/sky-pages.config', { 16 | spaPath: () => '', 17 | spaPathTempSrc: (...fragments) => ['src'].concat(fragments).join('/'), 18 | spaPathTemp: (...fragments) => fragments.join('/') 19 | }); 20 | util = require('../cli/utils/stage-library-ts'); 21 | }); 22 | 23 | afterEach(() => { 24 | mock.stopAll(); 25 | }); 26 | 27 | it('should return a function', () => { 28 | expect(typeof util).toEqual('function'); 29 | }); 30 | 31 | it('should copy source files from the public folder', () => { 32 | spyOn(glob, 'sync').and.returnValue([]); 33 | util(); 34 | expect(fs.copySync).toHaveBeenCalled(); 35 | }); 36 | 37 | it('should delete non-dist files', () => { 38 | const spy = spyOn(glob, 'sync').and.callFake((pattern) => { 39 | if (pattern.match('.spec.')) { 40 | return ['index.spec.ts']; 41 | } else { 42 | return []; 43 | } 44 | }); 45 | 46 | util(); 47 | expect(fs.removeSync).toHaveBeenCalled(); 48 | expect(spy).toHaveBeenCalledWith('/**/*.ts'); 49 | }); 50 | 51 | it('should fetch file contents for html and css files within angular components', () => { 52 | let finalContents; 53 | 54 | const spy = spyOn(glob, 'sync').and.callFake(pattern => { 55 | if (pattern.match('.spec.')) { 56 | return []; 57 | } else { 58 | return ['index.component.ts']; 59 | } 60 | }); 61 | 62 | spyOn(fs, 'readFileSync').and.callFake(filePath => { 63 | if (filePath === 'index.component.ts') { 64 | return ` 65 | @Component({ 66 | templateUrl: 'template.component.html', 67 | styleUrls: ['template.component.scss'] 68 | }) 69 | export class SampleComponent { } 70 | `; 71 | } 72 | 73 | if (filePath === 'template.component.html') { 74 | return '

'; 75 | } 76 | }); 77 | spyOn(fs, 'writeFileSync').and.callFake((file, contents) => { 78 | finalContents = contents; 79 | }); 80 | 81 | spyOn(sass, 'renderSync').and.returnValue({ 82 | css: 'p { color: black; }' 83 | }); 84 | 85 | util(); 86 | expect(finalContents.match('

')).not.toEqual(null); 87 | expect(finalContents.match('p { color: black; }')).not.toEqual(null); 88 | expect(spy).toHaveBeenCalledWith('/**/*.ts'); 89 | }); 90 | 91 | it('should handle multiline styleUrls array', () => { 92 | let finalContents; 93 | 94 | spyOn(glob, 'sync').and.callFake(pattern => { 95 | if (pattern.match('.spec.')) { 96 | return []; 97 | } else { 98 | return ['index.component.ts']; 99 | } 100 | }); 101 | 102 | spyOn(fs, 'readFileSync').and.callFake(filePath => { 103 | if (filePath === 'index.component.ts') { 104 | return ` 105 | @Component({ 106 | templateUrl: 'template.component.html', 107 | styleUrls: [ 108 | 'template.component.scss' 109 | ] 110 | }) 111 | export class SampleComponent { } 112 | `; 113 | } 114 | 115 | if (filePath === 'template.component.html') { 116 | return '

'; 117 | } 118 | }); 119 | 120 | spyOn(fs, 'writeFileSync').and.callFake((file, contents) => { 121 | finalContents = contents; 122 | }); 123 | 124 | spyOn(sass, 'renderSync').and.returnValue({ 125 | css: 'p { color: black; }' 126 | }); 127 | 128 | util(); 129 | expect(finalContents.match('p { color: black; }')).not.toEqual(null); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /test/cli-utils-ts-linter.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | const logger = require('@blackbaud/skyux-logger'); 6 | 7 | describe('cli util ts-linter', () => { 8 | afterEach(() => { 9 | mock.stopAll(); 10 | }); 11 | 12 | it('should expose a lintSync method', () => { 13 | spyOn(logger, 'info').and.returnValue(); 14 | mock('../config/sky-pages/sky-pages.config', { 15 | spaPath: (filePath) => filePath 16 | }); 17 | const tsLinter = mock.reRequire('../cli/utils/ts-linter'); 18 | expect(typeof tsLinter.lintSync).toEqual('function'); 19 | }); 20 | 21 | it('should spawn tslint', () => { 22 | let _executed = false; 23 | spyOn(logger, 'info').and.returnValue(); 24 | mock('../config/sky-pages/sky-pages.config', { 25 | spaPath: (filePath) => filePath 26 | }); 27 | mock('cross-spawn', { 28 | sync: () => { 29 | _executed = true; 30 | return { 31 | status: 0, 32 | output: [new Buffer('some error')] 33 | }; 34 | } 35 | }); 36 | const tsLinter = mock.reRequire('../cli/utils/ts-linter'); 37 | const result = tsLinter.lintSync(); 38 | expect(_executed).toEqual(true); 39 | expect(result.exitCode).toEqual(0); 40 | }); 41 | 42 | it('should log an error if linting errors found', () => { 43 | spyOn(logger, 'info').and.returnValue(); 44 | spyOn(logger, 'error').and.returnValue(); 45 | mock('../config/sky-pages/sky-pages.config', { 46 | spaPath: (filePath) => filePath 47 | }); 48 | mock('cross-spawn', { 49 | sync: () => { 50 | return { 51 | status: 1, 52 | output: [new Buffer('some error'), new Buffer('another error')] 53 | }; 54 | } 55 | }); 56 | const tsLinter = mock.reRequire('../cli/utils/ts-linter'); 57 | const result = tsLinter.lintSync(); 58 | expect(result.exitCode).toEqual(1); 59 | expect(logger.error).toHaveBeenCalled(); 60 | }); 61 | 62 | it('should not log an error if linting errors are not found', () => { 63 | spyOn(logger, 'info').and.returnValue(); 64 | spyOn(logger, 'error').and.returnValue(); 65 | mock('../config/sky-pages/sky-pages.config', { 66 | spaPath: (filePath) => filePath 67 | }); 68 | mock('cross-spawn', { 69 | sync: () => { 70 | return { 71 | status: 0, 72 | output: [null, new Buffer('')] 73 | }; 74 | } 75 | }); 76 | const tsLinter = mock.reRequire('../cli/utils/ts-linter'); 77 | const result = tsLinter.lintSync(); 78 | expect(result.exitCode).toEqual(0); 79 | expect(logger.error).not.toHaveBeenCalled(); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/cli-version.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | const proxyquire = require('proxyquire'); 6 | const logger = require('@blackbaud/skyux-logger'); 7 | 8 | describe('cli version', () => { 9 | it('should return the version from package.json', () => { 10 | spyOn(logger, 'info'); 11 | const version = 'this.should.match'; 12 | 13 | let stubs = {}; 14 | stubs[path.join(__dirname, '..', 'package.json')] = { 15 | '@noCallThru': true, 16 | version: version 17 | }; 18 | 19 | proxyquire('../cli/version', stubs)(); 20 | expect(logger.info).toHaveBeenCalledWith( 21 | '@blackbaud/skyux-builder: %s', 22 | version 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/config-axe.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | 6 | describe('config axe', () => { 7 | afterEach(() => { 8 | mock.stopAll(); 9 | }); 10 | 11 | it('should return a config object', () => { 12 | mock('../config/sky-pages/sky-pages.config', { 13 | getSkyPagesConfig: () => { 14 | return { 15 | skyux: {} 16 | }; 17 | } 18 | }); 19 | const lib = mock.reRequire('../config/axe/axe.config'); 20 | const config = lib.getConfig(); 21 | expect(config).toBeDefined(); 22 | expect(config.rules.label.enabled).toEqual(true); 23 | }); 24 | 25 | it('should merge config from a consuming SPA', () => { 26 | mock('../config/sky-pages/sky-pages.config', { 27 | getSkyPagesConfig: () => { 28 | return { 29 | skyux: { 30 | a11y: { 31 | rules: { 32 | label: { enabled: false } 33 | } 34 | } 35 | } 36 | }; 37 | } 38 | }); 39 | const lib = mock.reRequire('../config/axe/axe.config'); 40 | const config = lib.getConfig(); 41 | expect(config.rules.label.enabled).toEqual(false); 42 | }); 43 | 44 | it('should return defaults if rules are not defined', () => { 45 | mock('../config/sky-pages/sky-pages.config', { 46 | getSkyPagesConfig: () => { 47 | return { 48 | skyux: { 49 | a11y: {} 50 | } 51 | }; 52 | } 53 | }); 54 | const lib = mock.reRequire('../config/axe/axe.config'); 55 | const config = lib.getConfig(); 56 | expect(config.rules.label.enabled).toEqual(true); 57 | }); 58 | 59 | it('should disabled all rules if accessibility is set to false', () => { 60 | mock('../config/sky-pages/sky-pages.config', { 61 | getSkyPagesConfig: () => { 62 | return { 63 | skyux: { 64 | a11y: false 65 | } 66 | }; 67 | } 68 | }); 69 | const lib = mock.reRequire('../config/axe/axe.config'); 70 | const config = lib.getConfig(); 71 | expect(config.rules.label.enabled).toEqual(false); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/config-karma-test.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | 6 | describe('config karma test', () => { 7 | const path = '../config/karma/shared.karma.conf'; 8 | let called = false; 9 | 10 | beforeEach(() => { 11 | mock(path, () => { 12 | called = true; 13 | }); 14 | }); 15 | 16 | afterEach(() => { 17 | mock.stop(path); 18 | }); 19 | 20 | it('should load the shared config', (done) => { 21 | require('../config/karma/test.karma.conf')({ 22 | set: (config) => { 23 | expect(config.browsers).toBeDefined(); 24 | expect(called).toEqual(true); 25 | done(); 26 | } 27 | }); 28 | }); 29 | 30 | it('should use a custom launcher for Travis', (done) => { 31 | process.env.TRAVIS = true; 32 | require('../config/karma/test.karma.conf')({ 33 | set: (config) => { 34 | expect(config.browsers[0]).toBe('Chrome_travis_ci'); 35 | delete process.env.TRAVIS; 36 | done(); 37 | } 38 | }); 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /test/config-protractor.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | describe('config protractor test', () => { 5 | 6 | const mock = require('mock-require'); 7 | 8 | let lib; 9 | let config; 10 | 11 | beforeEach(() => { 12 | lib = mock.reRequire('../config/protractor/protractor.conf.js'); 13 | config = lib.config; 14 | }); 15 | 16 | afterEach(() => { 17 | mock.stopAll(); 18 | }); 19 | 20 | it('should return a config object', () => { 21 | expect(lib.config).toBeDefined(); 22 | }); 23 | 24 | it('should provide a method for beforeLaunch', () => { 25 | let called = false; 26 | mock('ts-node', { 27 | register: () => { 28 | called = true; 29 | } 30 | }); 31 | 32 | expect(config.beforeLaunch).toBeDefined(); 33 | config.beforeLaunch(); 34 | expect(called).toBe(true); 35 | }); 36 | 37 | it('should provide a method for onPrepare', () => { 38 | let called = false; 39 | spyOn(jasmine, 'getEnv').and.returnValue({ 40 | addReporter: () => { 41 | called = true; 42 | } 43 | }); 44 | 45 | config.onPrepare(); 46 | expect(jasmine.getEnv).toHaveBeenCalled(); 47 | expect(called).toEqual(true); 48 | }); 49 | 50 | it('should pass the logColor flag to the config', () => { 51 | mock('@blackbaud/skyux-logger', { logColor: false }); 52 | const lib = mock.reRequire('../config/protractor/protractor.conf.js'); 53 | expect(lib.config.jasmineNodeOpts.showColors).toBe(false); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/config-webpack-build-common.spec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackbaud/skyux-builder/828bfdb494dd6f075cb1ea78c03e45c1759b2c39/test/config-webpack-build-common.spec.js -------------------------------------------------------------------------------- /test/config-webpack-build-public-library.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | 6 | const runtimeUtils = require('../utils/runtime-test-utils'); 7 | 8 | describe('config webpack build public library', () => { 9 | const configPath = '../config/webpack/build-public-library.webpack.config'; 10 | 11 | let mockFs; 12 | let mockNgTools; 13 | let skyPagesConfig; 14 | 15 | beforeEach(() => { 16 | mockFs = { 17 | readJsonSync() { 18 | return {}; 19 | } 20 | }; 21 | 22 | mockNgTools = { 23 | AotPlugin: function () {} 24 | }; 25 | 26 | skyPagesConfig = { 27 | skyux: { 28 | mode: 'advanced' 29 | }, 30 | runtime: runtimeUtils.getDefaultRuntime() 31 | }; 32 | 33 | mock('../config/sky-pages/sky-pages.config', { 34 | spaPathTemp() { 35 | return 'temp'; 36 | }, 37 | spaPath() { 38 | return 'spa'; 39 | }, 40 | outPath() { 41 | return 'out'; 42 | } 43 | }); 44 | 45 | mock('fs-extra', mockFs); 46 | mock('@ngtools/webpack', mockNgTools); 47 | }); 48 | 49 | afterEach(() => { 50 | mock.stopAll(); 51 | }); 52 | 53 | it('should expose a getWebpackConfig method', () => { 54 | const lib = mock.reRequire(configPath); 55 | expect(typeof lib.getWebpackConfig).toEqual('function'); 56 | }); 57 | 58 | it('should return a config object', () => { 59 | const lib = mock.reRequire(configPath); 60 | const config = lib.getWebpackConfig(skyPagesConfig); 61 | expect(config).toEqual(jasmine.any(Object)); 62 | }); 63 | 64 | it('should use the name from skyuxconfig for the name of the module', () => { 65 | skyPagesConfig.skyux.name = 'sample-app'; 66 | const lib = mock.reRequire(configPath); 67 | const config = lib.getWebpackConfig(skyPagesConfig); 68 | expect(config.output.library).toBe('sample-app'); 69 | }); 70 | 71 | it('should use a default name if it is not set in skyuxconfig', () => { 72 | const lib = mock.reRequire(configPath); 73 | const config = lib.getWebpackConfig(skyPagesConfig); 74 | expect(config.output.library).toBe('SkyAppLibrary'); 75 | }); 76 | 77 | it('should generate externals from builder and SPA dependencies', () => { 78 | spyOn(mockFs, 'readJsonSync').and.callFake((path) => { 79 | if (path === 'out') { 80 | return { 81 | dependencies: { 82 | '@angular/common': '4.3.6', 83 | '@pact-foundation/pact-web': '5.3.0', 84 | 'zone.js': '0.8.10' 85 | }, 86 | peerDependencies: { 87 | '@angular/core': '4.3.6' 88 | } 89 | }; 90 | } 91 | 92 | return { 93 | dependencies: { 94 | '@blackbaud/skyux': '2.13.0', 95 | '@blackbaud-internal/skyux-lib-testing': 'latest' 96 | }, 97 | peerDependencies: { 98 | '@angular/core': '4.3.6' 99 | } 100 | }; 101 | }); 102 | const lib = mock.reRequire(configPath); 103 | const config = lib.getWebpackConfig(skyPagesConfig); 104 | expect(config.externals).toEqual([ 105 | /^@angular\/common/, 106 | /^@pact\-foundation\/pact\-web/, 107 | /^zone\.js/, 108 | /^@angular\/core/, 109 | /^@blackbaud\/skyux/, 110 | /^@blackbaud\-internal\/skyux\-lib\-testing/ 111 | ]); 112 | }); 113 | 114 | it('should handle externals if dependencies not defined', () => { 115 | const lib = mock.reRequire(configPath); 116 | const config = lib.getWebpackConfig(skyPagesConfig); 117 | expect(config.externals).toEqual([]); 118 | }); 119 | 120 | it('should setup AOT compilation', () => { 121 | const lib = mock.reRequire(configPath); 122 | const spy = spyOn(mockNgTools, 'AotPlugin').and.callThrough(); 123 | 124 | lib.getWebpackConfig(skyPagesConfig); 125 | 126 | expect(spy).toHaveBeenCalledWith({ 127 | tsConfigPath: 'temp', 128 | entryModule: 'temp#SkyLibPlaceholderModule', 129 | sourceMap: true 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /test/config-webpack-serve.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | const runtimeUtils = require('../utils/runtime-test-utils'); 6 | 7 | describe('config webpack serve', () => { 8 | 9 | it('should expose a getWebpackConfig method', () => { 10 | const lib = require('../config/webpack/serve.webpack.config'); 11 | expect(typeof lib.getWebpackConfig).toEqual('function'); 12 | }); 13 | 14 | it('should only open the browser once', () => { 15 | 16 | let browserSpy = jasmine.createSpy('browser'); 17 | mock('../cli/utils/browser', browserSpy); 18 | 19 | const lib = mock.reRequire('../config/webpack/serve.webpack.config'); 20 | const config = lib.getWebpackConfig({}, runtimeUtils.getDefault()); 21 | 22 | config.plugins.forEach(plugin => { 23 | if (plugin.name === 'WebpackPluginDone') { 24 | plugin.apply({ 25 | options: { 26 | appConfig: { 27 | base: 'my-custom-base' 28 | }, 29 | devServer: { 30 | port: 1234 31 | } 32 | }, 33 | plugin: (evt, cb) => { 34 | if (evt === 'done') { 35 | 36 | // Simulating a save by calling callback twice 37 | cb({ 38 | toJson: () => ({ 39 | chunks: [] 40 | }) 41 | }); 42 | 43 | cb({ 44 | toJson: () => ({ 45 | chunks: [] 46 | }) 47 | }); 48 | 49 | expect(browserSpy).toHaveBeenCalledTimes(1); 50 | } 51 | } 52 | }); 53 | } 54 | }); 55 | 56 | }); 57 | 58 | }); 59 | -------------------------------------------------------------------------------- /test/config-webpack-test.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | const runtimeUtils = require('../utils/runtime-test-utils'); 6 | 7 | describe('config webpack test', () => { 8 | let skyPagesConfig; 9 | 10 | beforeEach(() => { 11 | skyPagesConfig = { 12 | skyux: { 13 | mode: 'advanced' 14 | }, 15 | runtime: runtimeUtils.getDefaultRuntime() 16 | }; 17 | }); 18 | 19 | function getLib() { 20 | return require('../config/webpack/test.webpack.config'); 21 | } 22 | 23 | function getConfig(argv) { 24 | return getLib().getWebpackConfig(skyPagesConfig, argv); 25 | } 26 | 27 | function getInstrumentLoader(argv) { 28 | const config = getConfig(argv); 29 | 30 | return config.module.rules.filter(rule => { 31 | return (rule.use && rule.use[0].loader === 'istanbul-instrumenter-loader'); 32 | })[0]; 33 | } 34 | 35 | it('should expose a getWebpackConfig method', () => { 36 | const lib = getLib(); 37 | expect(typeof lib.getWebpackConfig).toEqual('function'); 38 | }); 39 | 40 | it('should return a config object', () => { 41 | const config = getConfig(); 42 | expect(config).toEqual(jasmine.any(Object)); 43 | }); 44 | 45 | it('should run coverage by default', () => { 46 | const instrumentLoader = getInstrumentLoader(); 47 | 48 | expect(instrumentLoader).toBeDefined(); 49 | }); 50 | 51 | it('should not run coverage if argv.coverage is false', () => { 52 | const instrumentLoader = getInstrumentLoader({ 53 | coverage: false 54 | }); 55 | 56 | expect(instrumentLoader).not.toBeDefined(); 57 | }); 58 | 59 | it('should match on whole folder names when excluding folders from code coverage', () => { 60 | function createTestPaths(items) { 61 | return [ 62 | ...items, 63 | // Add backslash variants of all provided paths. 64 | ...items.map(item => item.replace(/\//g, '\\')) 65 | ]; 66 | } 67 | 68 | const instrumentLoader = getInstrumentLoader(); 69 | 70 | const allowedPaths = createTestPaths([ 71 | '/home/not_node_modules/some-folder/test.ts', 72 | '/home/skyux-spa-testing/src/test.ts', 73 | '/src/app/do-not-exclude-this-fixtures-folder/test.ts', 74 | '/src/app/do-not-exclude-this-testing-folder/test.ts', 75 | '/src/app/libs/test.ts', 76 | '/src/app/some-other-index.ts' 77 | ]); 78 | 79 | const disallowedPaths = createTestPaths([ 80 | '/home/node_modules/some-folder/test.ts', 81 | '/home/testing/src/test.ts', 82 | '/src/app/fixtures/test.ts', 83 | '/src/app/testing/test.ts', 84 | '/src/app/lib/test.ts', 85 | '/src/app/index.ts' 86 | ]); 87 | 88 | for (const allowedPath of allowedPaths) { 89 | let foundMatch; 90 | 91 | for (const exclude of instrumentLoader.exclude) { 92 | foundMatch = foundMatch || exclude.test(allowedPath); 93 | } 94 | 95 | expect(foundMatch).toBe(false); 96 | } 97 | 98 | for (const disallowedPath of disallowedPaths) { 99 | let foundMatch; 100 | 101 | for (const exclude of instrumentLoader.exclude) { 102 | foundMatch = foundMatch || exclude.test(disallowedPath); 103 | } 104 | 105 | expect(foundMatch).toBe(true); 106 | } 107 | }); 108 | 109 | it('should run coverage differently for libraries', () => { 110 | let instrumentLoader = getInstrumentLoader(); 111 | let index = instrumentLoader.include.indexOf(path.resolve('src', 'app')); 112 | 113 | expect(index > -1).toEqual(true); 114 | 115 | instrumentLoader = getInstrumentLoader({ 116 | coverage: 'library' 117 | }); 118 | 119 | index = instrumentLoader.include.indexOf(path.resolve('src', 'app', 'public')); 120 | expect(index > -1).toEqual(true); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/fixtures/public/sky-pages-component-generator.fixture.component.ts: -------------------------------------------------------------------------------- 1 | // The contents of this file are stubbed in the unit test. 2 | -------------------------------------------------------------------------------- /test/index-ejs.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | const webpack = require('webpack'); 6 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 7 | const skyPagesConfigUtil = require('../config/sky-pages/sky-pages.config.js'); 8 | 9 | describe('index.ejs template', () => { 10 | 11 | it('should support external css & js in the correct locations', (done) => { 12 | 13 | mock('../config/webpack/build.webpack.config', { 14 | getWebpackConfig: (skyPagesConfig) => ({ 15 | entry: { 16 | test: ['test.js'] 17 | }, 18 | plugins: [ 19 | new HtmlWebpackPlugin({ 20 | template: 'src/main.ejs', 21 | inject: false, 22 | runtime: skyPagesConfig.runtime, 23 | skyux: skyPagesConfig.skyux 24 | }), 25 | function () { 26 | this.plugin('emit', (compilation) => { 27 | const source = compilation.assets['index.html'].source(); 28 | 29 | const css1 = ``; 30 | const css2 = ``; 31 | const js1 = ``; 32 | const js2 = ``; 33 | const js3 = ``; 34 | 35 | // Order 36 | const icss1 = source.indexOf(css1); 37 | const icss2 = source.indexOf(css2); 38 | const ijs1 = source.indexOf(js1); 39 | const ijs2 = source.indexOf(js2); 40 | const ijs3 = source.indexOf(js3); 41 | const ipp = source.indexOf(`__webpack_public_path__`); 42 | 43 | // CSS - Files + Integrity 44 | expect(source).toContain(css1); 45 | expect(source).toContain(css2); 46 | 47 | // JS - Files + Integrity 48 | expect(source).toContain(js1); 49 | expect(source).toContain(js2); 50 | expect(source).toContain(js3); 51 | 52 | // CSS - Order 53 | expect(icss1).toBeLessThan(icss2); 54 | 55 | // JS - Order 56 | expect(ijs1).toBeLessThan(ipp); 57 | expect(ijs2).toBeLessThan(ipp); 58 | expect(ipp).toBeLessThan(ijs3); 59 | 60 | done(); 61 | }); 62 | } 63 | ] 64 | }) 65 | }); 66 | 67 | mock('../cli/utils/ts-linter', { 68 | lintSync: () => 0 69 | }); 70 | 71 | let config = skyPagesConfigUtil.getSkyPagesConfig('build'); 72 | config.skyux = { 73 | app: { 74 | externals: { 75 | css: { 76 | before: [ 77 | { 78 | url: 'f1.css', 79 | integrity: 'ic1' 80 | } 81 | ], 82 | after: [ 83 | { 84 | url: 'f2.css' 85 | } 86 | ] 87 | }, 88 | js: { 89 | before: [ 90 | { 91 | url: 'f1.js', 92 | integrity: 'ic2', 93 | head: true 94 | }, 95 | { 96 | url: 'f2.js', 97 | integrity: 'ic3' 98 | } 99 | ], 100 | after: [ 101 | { 102 | url: 'f3.js' 103 | } 104 | ] 105 | } 106 | } 107 | } 108 | }; 109 | spyOn(process, 'exit').and.callFake(() => {}); 110 | mock.reRequire('../cli/build')({}, config, webpack); 111 | 112 | }); 113 | 114 | }); 115 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | const logger = require('@blackbaud/skyux-logger'); 6 | const config = require('../config/sky-pages/sky-pages.config'); 7 | 8 | describe('@blackbaud/skyux-builder', () => { 9 | 10 | it('should expose a runCommand method', () => { 11 | const lib = require('../index'); 12 | expect(typeof lib.runCommand).toEqual('function'); 13 | }); 14 | 15 | it('should handle known commands', () => { 16 | const lib = require('../index'); 17 | const cmds = { 18 | 'build': { 19 | cmd: 'build', 20 | lib: 'build' 21 | }, 22 | 'build-public-library': { 23 | cmd: 'build-public-library', 24 | lib: 'build-public-library' 25 | }, 26 | 'e2e': { 27 | cmd: 'e2e', 28 | lib: 'e2e' 29 | }, 30 | 'serve': { 31 | cmd: 'serve', 32 | lib: 'serve' 33 | }, 34 | 'lint': { 35 | cmd: 'lint', 36 | lib: 'lint' 37 | }, 38 | 'test': { 39 | cmd: 'test', 40 | lib: 'test' 41 | }, 42 | 'pact': { 43 | cmd: 'pact', 44 | lib: 'pact' 45 | }, 46 | 'watch': { 47 | cmd: 'watch', 48 | lib: 'test' 49 | }, 50 | 'version': { 51 | cmd: 'version', 52 | lib: 'version' 53 | }, 54 | 'generate': { 55 | cmd: 'generate', 56 | lib: 'generate' 57 | }, 58 | 'g': { 59 | cmd: 'generate', 60 | lib: 'generate' 61 | } 62 | }; 63 | 64 | Object.keys(cmds).forEach((key) => { 65 | mock('../cli/' + cmds[key].lib, () => { 66 | cmds[key].called = true; 67 | }); 68 | lib.runCommand(cmds[key].cmd, {}); 69 | expect(cmds[key].called).toEqual(true); 70 | }); 71 | }); 72 | 73 | it('should return false for unknown command', () => { 74 | spyOn(logger, 'info'); 75 | spyOn(config, 'getSkyPagesConfig'); 76 | 77 | const cmd = 'junk-command-that-does-not-exist'; 78 | const lib = require('../index'); 79 | 80 | expect(lib.runCommand(cmd, {})).toBe(false); 81 | expect(config.getSkyPagesConfig).not.toHaveBeenCalled(); 82 | }); 83 | 84 | it('should return true for known command', () => { 85 | spyOn(logger, 'info'); 86 | spyOn(config, 'getSkyPagesConfig'); 87 | 88 | const cmd = 'build'; 89 | const lib = require('../index'); 90 | 91 | expect(lib.runCommand(cmd, {})).toBe(true); 92 | expect(config.getSkyPagesConfig).toHaveBeenCalled(); 93 | }); 94 | 95 | it('should process shorthand tags', (done) => { 96 | const argv = { 97 | l: 'showForLaunch', 98 | b: 'showForBrowser', 99 | f: 'showForForce' 100 | }; 101 | mock('../cli/test', (c, a) => { 102 | expect(a.launch).toEqual(argv.l); 103 | expect(a.browser).toEqual(argv.b); 104 | expect(a.force).toEqual(argv.f); 105 | done(); 106 | }); 107 | const lib = require('../index'); 108 | lib.runCommand('test', argv); 109 | }); 110 | 111 | }); 112 | -------------------------------------------------------------------------------- /test/loader-assets.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | 6 | describe('SKY UX assets Webpack loader', () => { 7 | 8 | let loader; 9 | let mockLocaleProcessor; 10 | 11 | beforeEach(() => { 12 | mock('fs-extra', { 13 | readFileSync: function () { 14 | return 'zxcv'; 15 | } 16 | }); 17 | 18 | mock('hash-file', { 19 | sync: function () { 20 | return 'abcdefg'; 21 | } 22 | }); 23 | 24 | mock('loader-utils', { 25 | getOptions: function () { 26 | return { 27 | baseUrl: 'https://localhost:1234/base/' 28 | }; 29 | } 30 | }); 31 | 32 | mockLocaleProcessor = { 33 | isLocaleFile: () => false 34 | }; 35 | mock('../lib/locale-assets-processor', mockLocaleProcessor); 36 | 37 | mock.reRequire('../lib/assets-processor'); 38 | loader = mock.reRequire('../loader/sky-assets/index'); 39 | }); 40 | 41 | afterEach(() => { 42 | mock.stopAll(); 43 | }); 44 | 45 | it('should replace links to assets and emit the referenced files', () => { 46 | const html = ` 47 | 48 | 49 | 50 | `; 51 | 52 | const config = { 53 | _compiler: { 54 | plugin: function () { } 55 | }, 56 | options: { 57 | SKY_PAGES: { 58 | entries: [] 59 | } 60 | }, 61 | emitFile: function () { } 62 | }; 63 | 64 | const emitFileSpy = spyOn(config, 'emitFile'); 65 | 66 | let modifiedHtml = loader.apply(config, [html]); 67 | 68 | // Verify the referenced file is emitted to Webpack's output. 69 | expect(emitFileSpy.calls.count()).toBe(3); 70 | 71 | expect(emitFileSpy.calls.argsFor(0)).toEqual(['assets/image.abcdefg.jpg', 'zxcv']); 72 | expect(emitFileSpy.calls.argsFor(1)).toEqual(['assets/image2.abcdefg.jpg', 'zxcv']); 73 | expect(emitFileSpy.calls.argsFor(2)).toEqual(['assets/image.3.abcdefg.jpg', 'zxcv']); 74 | 75 | // Verify that the references were updated to include the base URL and hash. 76 | expect(modifiedHtml).toBe(` 77 | 78 | 79 | 80 | ` 81 | ); 82 | }); 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /test/loader-module.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | describe('SKY UX Builder Webpack module loader', () => { 5 | 6 | let loader; 7 | beforeEach(() => { 8 | loader = require('../loader/sky-pages-module/index'); 9 | }); 10 | 11 | it('should call the SKY UX Builder module generator', () => { 12 | const generator = require('../lib/sky-pages-module-generator'); 13 | 14 | let getSourceSpy = spyOn(generator, 'getSource'); 15 | 16 | const config = { 17 | _compiler: { 18 | plugin: function () {} 19 | }, 20 | options: { 21 | skyPagesConfig: { 22 | entries: [] 23 | } 24 | } 25 | }; 26 | 27 | loader.apply(config); 28 | 29 | expect(getSourceSpy).toHaveBeenCalledWith(config.options.skyPagesConfig); 30 | }); 31 | 32 | it('should timestamp sky-pages.module.ts for HTML and TS updates', () => { 33 | 34 | const fs = require('fs'); 35 | const generator = require('../lib/sky-pages-module-generator'); 36 | 37 | spyOn(generator, 'getSource'); 38 | spyOn(fs, 'writeFileSync'); 39 | 40 | let callback; 41 | 42 | const config = { 43 | _compiler: { 44 | plugin: function (evt, cb) { 45 | callback = cb; 46 | } 47 | }, 48 | options: { 49 | SKY_PAGES: { 50 | entries: [] 51 | } 52 | } 53 | }; 54 | 55 | loader.apply(config); 56 | callback('my-file.html'); 57 | callback('sky-pages.module.ts'); 58 | callback('my-file.ts'); 59 | expect(fs.writeFileSync).toHaveBeenCalledTimes(2); 60 | 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/plugin-file-processor.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | const fs = require('fs-extra'); 6 | const glob = require('glob'); 7 | 8 | describe('SKY UX plugin file processor', () => { 9 | const processorPath = '../lib/plugin-file-processor'; 10 | const content = ''; 11 | let config; 12 | 13 | beforeEach(() => { 14 | mock('../config/sky-pages/sky-pages.config', { 15 | spaPathTempSrc() { 16 | return '.skypagestemp/src/app'; 17 | } 18 | }); 19 | 20 | mock('../loader/sky-processor', { 21 | preload: () => 'changed content' 22 | }); 23 | 24 | config = { 25 | resourcePath: '', 26 | options: { 27 | skyPagesConfig: { 28 | skyux: {} 29 | } 30 | } 31 | }; 32 | 33 | spyOn(fs, 'writeFileSync').and.callFake(() => {}); 34 | }); 35 | 36 | afterEach(() => { 37 | mock.stopAll(); 38 | }); 39 | 40 | it('should return a method', () => { 41 | const processor = mock.reRequire(processorPath); 42 | expect(typeof processor.processFiles).toEqual('function'); 43 | }); 44 | 45 | it('should generate an array of file paths and contents for the SPA\'s source', () => { 46 | spyOn(glob, 'sync').and.returnValue([ 'my-file.js' ]); 47 | spyOn(fs, 'readFileSync').and.returnValue(''); 48 | const processor = mock.reRequire(processorPath); 49 | processor.processFiles(config); 50 | expect(glob.sync).toHaveBeenCalled(); 51 | expect(fs.readFileSync).toHaveBeenCalled(); 52 | }); 53 | 54 | it('should handle an invalid root directory', () => { 55 | spyOn(glob, 'sync').and.callThrough(); 56 | const processor = mock.reRequire(processorPath); 57 | processor.processFiles(config); 58 | expect(fs.readdirSync).toThrow(); 59 | }); 60 | 61 | it('should allow plugin preload hooks to alter the content', () => { 62 | spyOn(glob, 'sync').and.returnValue([ 'my-file.js' ]); 63 | spyOn(fs, 'readFileSync').and.returnValue(''); 64 | const processor = mock.reRequire(processorPath); 65 | processor.processFiles(config); 66 | expect(fs.writeFileSync).toHaveBeenCalled(); 67 | }); 68 | 69 | it('should not alter the content of a file if nothing has changed', () => { 70 | spyOn(glob, 'sync').and.returnValue([ 'my-file.js' ]); 71 | spyOn(fs, 'readFileSync').and.returnValue('changed content'); 72 | const processor = mock.reRequire(processorPath); 73 | processor.processFiles(config); 74 | expect(fs.writeFileSync).not.toHaveBeenCalled(); 75 | }); 76 | 77 | it('should allow for a custom root directory', () => { 78 | const spy = spyOn(glob, 'sync').and.callThrough(); 79 | const processor = mock.reRequire(processorPath); 80 | processor.processFiles(config, 'foo/bar'); 81 | expect(spy.calls.argsFor(0)[0]).toEqual('foo/bar'); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/plugin-output-keep-alive.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | 6 | describe('SKY UX plugin file processor', () => { 7 | let _compilerHooksCalled; 8 | let _compilationHooksCalled; 9 | 10 | let _mockCompiler; 11 | beforeEach(() => { 12 | _compilerHooksCalled = []; 13 | _compilationHooksCalled = []; 14 | 15 | _mockCompiler = { 16 | plugin(hook, callback) { 17 | _compilerHooksCalled.push(hook); 18 | callback({ 19 | plugin(hook, callback) { 20 | _compilationHooksCalled.push(hook); 21 | callback(); 22 | } 23 | }); 24 | } 25 | }; 26 | }); 27 | 28 | afterEach(() => { 29 | mock.stopAll(); 30 | }); 31 | 32 | it('should return a constructor', () => { 33 | const { OutputKeepAlivePlugin } = mock.reRequire('../plugin/output-keep-alive'); 34 | expect(typeof OutputKeepAlivePlugin.constructor).toBeDefined(); 35 | }); 36 | 37 | it('should output to the console', () => { 38 | const stdoutSpy = spyOn(process.stdout, 'write').and.callThrough(); 39 | const { OutputKeepAlivePlugin } = mock.reRequire('../plugin/output-keep-alive'); 40 | const plugin = new OutputKeepAlivePlugin({ enabled: true }); 41 | 42 | plugin.apply(_mockCompiler); 43 | 44 | expect(stdoutSpy.calls.count()).toEqual(1); 45 | expect(_compilerHooksCalled).toEqual(['compilation']); 46 | expect(_compilationHooksCalled).toEqual(['build-module']); 47 | }); 48 | 49 | it('should not output to the console if disabled', () => { 50 | const stdoutSpy = spyOn(process.stdout, 'write').and.callThrough(); 51 | const { OutputKeepAlivePlugin } = mock.reRequire('../plugin/output-keep-alive'); 52 | const plugin = new OutputKeepAlivePlugin(); 53 | 54 | plugin.apply(_mockCompiler); 55 | 56 | expect(stdoutSpy).not.toHaveBeenCalled(); 57 | expect(_compilerHooksCalled).toEqual([]); 58 | expect(_compilationHooksCalled).toEqual([]); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/sky-pages-assets-generator.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const mock = require('mock-require'); 5 | const glob = require('glob'); 6 | const skyPagesConfigUtil = require('../config/sky-pages/sky-pages.config'); 7 | const localeAssetsProcessor = require('../lib/locale-assets-processor'); 8 | 9 | describe('SKY UX Builder assets generator', () => { 10 | let mockLocaleProcessor; 11 | 12 | beforeEach(() => { 13 | mockLocaleProcessor = { 14 | getDefaultLocaleFiles: localeAssetsProcessor.getDefaultLocaleFiles, 15 | isLocaleFile() { 16 | return false; 17 | }, 18 | parseLocaleFileBasename() { 19 | return 'BASENAME'; 20 | } 21 | }; 22 | 23 | mock('../lib/locale-assets-processor', mockLocaleProcessor); 24 | 25 | spyOn(skyPagesConfigUtil, 'spaPath').and.callFake((...args) => { 26 | return '/root/' + args.join('/'); 27 | }); 28 | }); 29 | 30 | afterEach(() => { 31 | mock.stopAll(); 32 | }); 33 | 34 | it('should emit the expected code', () => { 35 | spyOn(glob, 'sync').and.callFake((pattern) => { 36 | return pattern.indexOf('.skypageslocales') > -1 ? [] : [ 37 | '/root/src/assets/a/b/c/d.jpg', 38 | '/root/src/assets/e/f.jpg' 39 | ]; 40 | }); 41 | 42 | const generator = mock.reRequire('../lib/sky-pages-assets-generator'); 43 | const source = generator.getSource(); 44 | 45 | expect(source).toBe( 46 | `export class ${generator.getClassName()} { 47 | public getUrl(filePath: string): string { 48 | const pathMap: {[key: string]: any} = { 49 | 'a/b/c/d.jpg': '~/assets/a/b/c/d.jpg', 50 | 'e/f.jpg': '~/assets/e/f.jpg' 51 | }; 52 | 53 | return pathMap[filePath]; 54 | } 55 | }` 56 | ); 57 | }); 58 | 59 | it('should handle merged locale files', () => { 60 | spyOn(mockLocaleProcessor, 'isLocaleFile').and.returnValue(true); 61 | spyOn(glob, 'sync').and.callFake(() => { 62 | return [ 63 | '/root/src/assets/locales/resources_en-US.json', 64 | '/root/src/assets/locales/resources_fr_CA.json' 65 | ]; 66 | }); 67 | 68 | const generator = mock.reRequire('../lib/sky-pages-assets-generator'); 69 | const source = generator.getSource(); 70 | 71 | // The 'BASENAME' is provided by the locale assets processor. 72 | // This test ensures that the file name (and lookup key) 73 | // is governed by the locale assets processor. 74 | expect(source).toBe( 75 | `export class SkyAppAssetsImplService { 76 | public getUrl(filePath: string): string { 77 | const pathMap: {[key: string]: any} = { 78 | 'locales/BASENAME': '~/assets/BASENAME', 79 | 'locales/BASENAME': '~/assets/BASENAME' 80 | }; 81 | 82 | return pathMap[filePath]; 83 | } 84 | }` 85 | ); 86 | }); 87 | 88 | it('should include the auto-generated locale file if the site does not have one', () => { 89 | spyOn(mockLocaleProcessor, 'isLocaleFile').and.callFake(file => { 90 | return file.indexOf('.skypageslocales') > -1; 91 | }); 92 | spyOn(glob, 'sync').and.callFake((pattern) => { 93 | return pattern.indexOf('.skypageslocales') > -1 94 | ? ['/root/.skypageslocales/resources_en_US.json'] 95 | : []; 96 | }); 97 | 98 | const generator = mock.reRequire('../lib/sky-pages-assets-generator'); 99 | const source = generator.getSource(); 100 | 101 | // The 'BASENAME' is provided by the locale assets processor. 102 | // This test ensures that the file name (and lookup key) 103 | // is governed by the locale assets processor. 104 | expect(source).toBe( 105 | `export class SkyAppAssetsImplService { 106 | public getUrl(filePath: string): string { 107 | const pathMap: {[key: string]: any} = { 108 | 'locales/BASENAME': '~/assets/BASENAME' 109 | }; 110 | 111 | return pathMap[filePath]; 112 | } 113 | }` 114 | ); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /test/utils-host-utils.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const mock = require('mock-require'); 6 | 7 | describe('host-utils', () => { 8 | 9 | const skyPagesConfig = { 10 | skyux: { 11 | name: 'my-spa-name', 12 | host: { 13 | url: 'base.com' 14 | } 15 | } 16 | }; 17 | 18 | function decode(url) { 19 | return JSON.parse(Buffer.from(url.split('_cfg=')[1], 'base64')); 20 | } 21 | 22 | let utils; 23 | beforeEach(() => { 24 | mock('html-webpack-plugin/lib/chunksorter', { 25 | dependency: (chunks) => chunks 26 | }); 27 | utils = require('../utils/host-utils'); 28 | }); 29 | 30 | afterEach(() => { 31 | utils = null; 32 | mock.stop('html-webpack-plugin/lib/chunksorter'); 33 | }); 34 | 35 | it('should resolve a url, trim trailing slash from host and leading slash from url', () => { 36 | const resolved = utils.resolve('/url?q=1', '', [], { 37 | skyux: { 38 | name: 'cool-spa', 39 | host: { 40 | url: 'my-base.com/' 41 | } 42 | } 43 | }); 44 | expect(resolved).toContain(`my-base.com/cool-spa/url?q=1&local=true&_cfg=`); 45 | }); 46 | 47 | it('should resolve a url without a querystring', () => { 48 | const resolved = utils.resolve('url', '', [], skyPagesConfig); 49 | expect(resolved).toContain(`base.com/my-spa-name/url?local=true&_cfg=`); 50 | }); 51 | 52 | it('should resolve a url with a querystring', () => { 53 | const resolved = utils.resolve('/url?q=1', '', [], skyPagesConfig); 54 | expect(resolved).toContain(`base.com/my-spa-name/url?q=1&local=true&_cfg=`); 55 | }); 56 | 57 | it('should add scripts / chunks', () => { 58 | 59 | const resolved = utils.resolve('/url', '', [{ files: ['test.js'] }], skyPagesConfig); 60 | const decoded = decode(resolved); 61 | 62 | expect(resolved).toContain(`base.com/my-spa-name/url?local=true&_cfg=`); 63 | expect(decoded.scripts).toEqual([{ name: 'test.js' }]); 64 | }); 65 | 66 | it('should return metadata if provided chunks contain metadata property', () => { 67 | const json = { 68 | metadata: [{ 69 | name: 'file1.js' 70 | }] 71 | }; 72 | 73 | const resolved = utils.resolve('/url', '', json, skyPagesConfig); 74 | const decoded = decode(resolved); 75 | 76 | expect(decoded.scripts).toEqual(json.metadata); 77 | }); 78 | 79 | it('should add externals, trim slash from host, and read name from package.json', () => { 80 | 81 | const readJsonSync = fs.readJsonSync; 82 | spyOn(fs, 'readJsonSync').and.callFake((filename, encoding) => { 83 | if (filename.indexOf('package.json') > -1) { 84 | return { 85 | name: 'my-name' 86 | }; 87 | } 88 | 89 | return readJsonSync(filename, encoding); 90 | }); 91 | 92 | const externals = { 93 | js: [{ 94 | head: true, 95 | url: 'myjs.com' 96 | }] 97 | }; 98 | const resolved = utils.resolve('/url', '', [], { 99 | skyux: { 100 | app: { 101 | externals: externals 102 | }, 103 | host: { 104 | url: 'base.com/' // Testing this goes away 105 | } 106 | } 107 | }); 108 | const decoded = decode(resolved); 109 | 110 | expect(resolved).toContain(`base.com/my-name/url?local=true&_cfg=`); 111 | expect(decoded.externals).toEqual(externals); 112 | mock.stop('../package.json'); 113 | }); 114 | 115 | }); 116 | -------------------------------------------------------------------------------- /test/utils-merge-utils.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | const merge = require('../utils/merge'); 5 | 6 | describe('merge-utils', () => { 7 | it('should return a merged object with overridden arrays', () => { 8 | let original = { 9 | arr: ['stringOne', 'stringTwo'], 10 | objArr: [{ 11 | test: 'test' 12 | }, 13 | { 14 | test: 'testTwo' 15 | }] 16 | }; 17 | let override = { 18 | arr: ['stringThree'], 19 | objArr: [{ 20 | test: 'override' 21 | }, 22 | { 23 | test: 'changed', 24 | nested: { 25 | deep: 'nested key' 26 | } 27 | }] 28 | }; 29 | 30 | let result = merge(original, override); 31 | expect(result.arr.length).toBe(1); 32 | expect(result.arr).not.toContain('stringTwo'); 33 | expect(result.objArr.length).toBe(2); 34 | expect(result.objArr[0].test).toBe('override'); 35 | expect(result.objArr[1].nested.deep).toBe('nested key'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/utils-pact-servers.spec.js: -------------------------------------------------------------------------------- 1 | /*jshint jasmine: true, node: true */ 2 | 'use strict'; 3 | 4 | describe('pact-servers', () => { 5 | 6 | it('should save and get pact servers', () => { 7 | 8 | const pactServers = require('../utils/pact-servers'); 9 | 10 | pactServers.savePactServer('test-provider', 'localhost', '1234'); 11 | 12 | expect(pactServers.getPactServer('test-provider').fullUrl).toEqual('http://localhost:1234'); 13 | expect(pactServers.getPactServer('test-provider').host).toEqual('localhost'); 14 | expect(pactServers.getPactServer('test-provider').port).toEqual('1234'); 15 | 16 | }); 17 | 18 | it('should save and get pact proxy server', () => { 19 | 20 | const pactServers = require('../utils/pact-servers'); 21 | 22 | pactServers.savePactProxyServer('http://localhost:8000'); 23 | 24 | expect(pactServers.getPactProxyServer()).toEqual('http://localhost:8000'); 25 | 26 | }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "sourceMap": true, 9 | "noEmitHelpers": true, 10 | "noEmitOnError": true, 11 | "noUnusedLocals": true, 12 | "noImplicitAny": true, 13 | "noResolve": false, 14 | "lib": [ 15 | "dom", 16 | "es6" 17 | ], 18 | "types": [ 19 | "core-js", 20 | "node", 21 | "jasmine" 22 | ], 23 | "baseUrl": ".", 24 | "paths": { 25 | "@blackbaud/skyux-builder/*": [ 26 | "./node_modules/@blackbaud/skyux-builder/*", 27 | "./*" 28 | ], 29 | ".skypageslocales/*": [ 30 | ".skypageslocales/*" 31 | ] 32 | } 33 | }, 34 | "exclude": [ 35 | "node_modules", 36 | "**/*.aot.ts" 37 | ], 38 | "compileOnSave": false, 39 | "buildOnSave": false 40 | } 41 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "codelyzer", 4 | "tslint-jasmine-rules" 5 | ], 6 | "rules": { 7 | "member-access": true, 8 | "member-ordering": [ 9 | true, 10 | { 11 | "order": [ 12 | "static-field", 13 | "instance-field", 14 | "constructor", 15 | "public-instance-method", 16 | "protected-instance-method", 17 | "private-instance-method" 18 | ] 19 | } 20 | ], 21 | "no-any": false, 22 | "no-inferrable-types": false, 23 | "no-internal-module": true, 24 | "no-reference": true, 25 | "no-var-requires": false, 26 | "typedef": [false], 27 | "typedef-whitespace": [ 28 | true, 29 | { 30 | "call-signature": "nospace", 31 | "index-signature": "nospace", 32 | "parameter": "nospace", 33 | "property-declaration": "nospace", 34 | "variable-declaration": "nospace" 35 | }, 36 | { 37 | "call-signature": "space", 38 | "index-signature": "space", 39 | "parameter": "space", 40 | "property-declaration": "space", 41 | "variable-declaration": "space" 42 | } 43 | ], 44 | 45 | "ban": false, 46 | "curly": true, 47 | "forin": true, 48 | "label-position": true, 49 | "no-arg": true, 50 | "no-bitwise": true, 51 | "no-conditional-assignment": true, 52 | "no-console": [ 53 | true, 54 | "debug", 55 | "info", 56 | "time", 57 | "timeEnd", 58 | "trace" 59 | ], 60 | "no-construct": true, 61 | "no-debugger": true, 62 | "no-duplicate-variable": true, 63 | "no-empty": false, 64 | "no-eval": true, 65 | "no-null-keyword": true, 66 | "no-shadowed-variable": true, 67 | "no-string-literal": false, 68 | "no-switch-case-fall-through": true, 69 | "no-unused-expression": true, 70 | "no-unused-variable": true, 71 | "no-use-before-declare": true, 72 | "no-var-keyword": true, 73 | "radix": true, 74 | "switch-default": true, 75 | "triple-equals": [ 76 | true, 77 | "allow-null-check" 78 | ], 79 | "eofline": true, 80 | "indent": [ 81 | true, 82 | "spaces" 83 | ], 84 | "max-line-length": [ 85 | true, 86 | 140 87 | ], 88 | "no-require-imports": false, 89 | "no-trailing-whitespace": true, 90 | "object-literal-sort-keys": false, 91 | "trailing-comma": [ 92 | true, 93 | { 94 | "multiline": "never", 95 | "singleline": "never" 96 | } 97 | ], 98 | 99 | "align": [false], 100 | "class-name": true, 101 | "comment-format": [ 102 | true, 103 | "check-space" 104 | ], 105 | "interface-name": [ 106 | true, "never-prefix" 107 | ], 108 | "jsdoc-format": true, 109 | "no-consecutive-blank-lines": true, 110 | "no-parameter-properties": false, 111 | "one-line": [ 112 | true, 113 | "check-open-brace", 114 | "check-catch", 115 | "check-else", 116 | "check-finally", 117 | "check-whitespace" 118 | ], 119 | "quotemark": [ 120 | true, 121 | "single", 122 | "avoid-escape" 123 | ], 124 | "semicolon": [true, "always"], 125 | "variable-name": [ 126 | true, 127 | "check-format", 128 | "allow-leading-underscore", 129 | "ban-keywords" 130 | ], 131 | "whitespace": [ 132 | true, 133 | "check-branch", 134 | "check-decl", 135 | "check-operator", 136 | "check-separator", 137 | "check-type" 138 | ], 139 | "directive-selector": [true, "attribute", "", "camelCase"], 140 | "component-selector": [true, "element", "", "kebab-case"], 141 | "use-input-property-decorator": true, 142 | "use-output-property-decorator": true, 143 | "use-host-property-decorator": true, 144 | "no-attribute-parameter-decorator": true, 145 | "no-input-rename": true, 146 | "no-output-rename": true, 147 | "no-forward-ref": true, 148 | "use-life-cycle-interface": true, 149 | "use-pipe-transform-interface": true, 150 | "pipe-naming": [true, "camelCase", "sky"], 151 | "component-class-suffix": true, 152 | "directive-class-suffix": true, 153 | "no-focused-tests": true, 154 | "no-disabled-tests": { 155 | "severity": "warning" 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /utils/assets-utils.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const hashFile = require('hash-file'); 5 | const skyPagesConfigUtil = require('../config/sky-pages/sky-pages.config'); 6 | 7 | /** 8 | * Appends the hash of the specified file to the end of the file path. 9 | * @param {*} filePath The path to the file. 10 | * @param {*} rel Optional Boolean flag indicating whether the returned path should be relative 11 | * to the app's src path. 12 | */ 13 | function getFilePathWithHash(filePath, rel) { 14 | const indexOfLastDot = filePath.lastIndexOf('.'); 15 | 16 | let filePathWithHash = filePath.substr(0, indexOfLastDot) + 17 | '.' + 18 | hashFile.sync(skyPagesConfigUtil.spaPath('src', filePath)) + 19 | '.' + 20 | filePath.substr(indexOfLastDot + 1); 21 | 22 | if (!rel) { 23 | const srcPath = skyPagesConfigUtil.spaPath('src'); 24 | filePathWithHash = filePathWithHash.substr(srcPath.length + 1); 25 | } 26 | 27 | return filePathWithHash; 28 | } 29 | 30 | /** 31 | * Gets the URL to a hashed file name. 32 | * @param {*} baseUrl The base of the URL where the page is being served. 33 | * @param {*} filePath The path to the file. 34 | */ 35 | function getUrl(baseUrl, filePath) { 36 | const filePathWithHash = getFilePathWithHash(filePath); 37 | 38 | const url = `${baseUrl}${filePathWithHash.replace(/\\/gi, '/')}`; 39 | 40 | return url; 41 | } 42 | 43 | module.exports = { 44 | getFilePathWithHash: getFilePathWithHash, 45 | getUrl: getUrl 46 | }; 47 | -------------------------------------------------------------------------------- /utils/cli-test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | // Runs the equivalent of `skyux test`, but passes in the third argument as the command. 5 | require('../cli/test')(process.argv[2]); 6 | 7 | -------------------------------------------------------------------------------- /utils/codegen-utils.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | /** 5 | * Indents a string the specified number of "tabs" (two spaces = one tab). 6 | * @param {*} count The number of "tabs" to indent. 7 | * @param {*} s The string to indent. 8 | */ 9 | function indent(count, s) { 10 | return ' '.repeat(count) + (s || ''); 11 | } 12 | 13 | module.exports = { 14 | indent: indent 15 | }; 16 | -------------------------------------------------------------------------------- /utils/host-utils.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | // HTML Webpack Plugin has already solved sorting the entries 5 | const sorter = require('html-webpack-plugin/lib/chunksorter'); 6 | const skyPagesConfigUtil = require('../config/sky-pages/sky-pages.config'); 7 | /** 8 | * Creates a resolved host url. 9 | * @param {string} url 10 | * @param {string} localUrl 11 | * @param {Array} chunks 12 | * @param {Object} skyPagesConfig 13 | */ 14 | function resolve(url, localUrl, chunks, skyPagesConfig) { 15 | let host = skyPagesConfig.skyux.host.url; 16 | let config = { 17 | scripts: getScripts(chunks), 18 | localUrl: localUrl 19 | }; 20 | 21 | if (skyPagesConfig.skyux.app && skyPagesConfig.skyux.app.externals) { 22 | config.externals = skyPagesConfig.skyux.app.externals; 23 | } 24 | 25 | // Trim leading slash since getAppBase adds it 26 | if (url && url.charAt(0) === '/') { 27 | url = url.substring(1); 28 | } 29 | 30 | // Trim trailing slash since geAppBase adds it 31 | if (host && host.charAt(host.length - 1) === '/') { 32 | host = host.slice(0, -1); 33 | } 34 | 35 | const delimeter = url.indexOf('?') === -1 ? '?' : '&'; 36 | const encoded = new Buffer(JSON.stringify(config)).toString('base64'); 37 | const base = skyPagesConfigUtil.getAppBase(skyPagesConfig); 38 | const resolved = `${host}${base}${url}${delimeter}local=true&_cfg=${encoded}`; 39 | 40 | return resolved; 41 | } 42 | 43 | /** 44 | * Sorts chunks into array of scripts. 45 | * @param {Array} chunks 46 | */ 47 | function getScripts(chunks) { 48 | let scripts = []; 49 | 50 | // Used when skipping the build, short-circuit to return metadata 51 | if (chunks.metadata) { 52 | return chunks.metadata; 53 | } 54 | 55 | sorter.dependency(chunks).forEach((chunk) => { 56 | scripts.push({ 57 | name: chunk.files[0] 58 | }); 59 | }); 60 | 61 | return scripts; 62 | } 63 | 64 | module.exports = { 65 | resolve: resolve, 66 | getScripts: getScripts 67 | }; 68 | -------------------------------------------------------------------------------- /utils/merge.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | const merge = require('lodash.mergewith'); 5 | 6 | function customizer(originalValue, overrideValue) { 7 | if (Array.isArray(originalValue)) { 8 | return overrideValue; 9 | } 10 | } 11 | 12 | const mergeWith = function (original, override) { 13 | return merge(original, override, customizer); 14 | }; 15 | 16 | module.exports = mergeWith; 17 | -------------------------------------------------------------------------------- /utils/pact-servers.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | let pactServers = {}; 5 | let pactProxyServer = ''; 6 | 7 | /** 8 | * Utility to carry pact information determined in pact.js over into the pact.karma.conf.js 9 | */ 10 | module.exports = { 11 | 12 | /** 13 | * Saves the pact server object temporarily 14 | * 15 | * @param {string} providerName - Name of the provider this server will mock 16 | * @param {string} host - The host of the pact server 17 | * @param {string|number} port - The port of the pact server 18 | */ 19 | savePactServer: (providerName, host, port) => { 20 | pactServers[providerName] = { host: host, port: port, fullUrl: `http://${host}:${port}` }; 21 | }, 22 | 23 | /** 24 | * Saves the url to the proxy server that manages requests to the pact servers 25 | * 26 | * @param {string} url - The url of the proxy server 27 | */ 28 | savePactProxyServer: (url) => { 29 | pactProxyServer = url; 30 | }, 31 | 32 | /** 33 | * Returns the url to the pact proxy server 34 | * 35 | * @returns {string} the url 36 | */ 37 | getPactProxyServer: () => pactProxyServer, 38 | 39 | /** 40 | * Returns the pact object for the desired provider 41 | * 42 | * @param {string} providerName - The name of the provider 43 | */ 44 | getPactServer: (providerName) => pactServers[providerName], 45 | 46 | /** 47 | * Returns all recorded pact objects 48 | * 49 | * @returns {Object} All recorded pact servers for test run 50 | */ 51 | getAllPactServers: () => pactServers 52 | 53 | }; 54 | -------------------------------------------------------------------------------- /utils/runtime-test-skyux.css: -------------------------------------------------------------------------------- 1 | /* 2 | This is a mock for the `require('skyux.css`) in `app.component.ts`. 3 | */ 4 | -------------------------------------------------------------------------------- /utils/runtime-test-utils.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | const merge = require('../utils/merge'); 5 | 6 | module.exports = { 7 | getDefault: function (runtime, skyux) { 8 | return { 9 | runtime: this.getDefaultRuntime(runtime), 10 | skyux: this.getDefaultSkyux(skyux) 11 | }; 12 | }, 13 | 14 | getDefaultRuntime: function (runtime) { 15 | return merge({ 16 | app: { 17 | base: '', 18 | inject: false, 19 | template: '' 20 | }, 21 | command: '', 22 | componentsPattern: '**/*.component.ts', 23 | componentsIgnorePattern: './public/**/*', 24 | includeRouteModule: true, 25 | routes: [], 26 | routesPattern: '**/index.html', 27 | runtimeAlias: 'sky-pages-internal/runtime', 28 | srcPath: 'src/app/', 29 | spaPathAlias: 'sky-pages-spa', 30 | skyPagesOutAlias: 'sky-pages-internal', 31 | skyuxPathAlias: '@blackbaud/skyux/dist', 32 | useTemplateUrl: false 33 | }, runtime); 34 | }, 35 | 36 | getDefaultSkyux: function (skyux) { 37 | return merge({ 38 | host: { 39 | url: '' 40 | }, 41 | mode: '', 42 | params: [ 43 | 'envid', 44 | 'svcid' 45 | ], 46 | skyuxModules: [ 47 | 'SkyModule' 48 | ] 49 | }, skyux); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /utils/spec-bundle.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | /*global ROOT_DIR*/ 3 | /*global skyPagesConfig*/ 4 | 'use strict'; 5 | 6 | /** 7 | * @author: @AngularClass 8 | */ 9 | 10 | /* 11 | * When testing with webpack and ES6, we have to do some extra 12 | * things get testing to work right. Because we are gonna write test 13 | * in ES6 to, we have to compile those as well. That's handled in 14 | * karma.conf.js with the karma-webpack plugin. This is the entry 15 | * file for webpack test. Just like webpack will create a bundle.js 16 | * file for our client, when we run test, it well compile and bundle them 17 | * all here! Crazy huh. So we need to do some setup 18 | */ 19 | Error.stackTraceLimit = Infinity; 20 | 21 | require('core-js'); 22 | 23 | // Typescript emit helpers polyfill 24 | require('ts-helpers'); 25 | 26 | require('zone.js/dist/zone'); 27 | require('zone.js/dist/long-stack-trace-zone'); 28 | require('zone.js/dist/async-test'); 29 | require('zone.js/dist/fake-async-test'); 30 | require('zone.js/dist/sync-test'); 31 | require('zone.js/dist/proxy'); 32 | require('zone.js/dist/jasmine-patch'); 33 | 34 | require('reflect-metadata'); 35 | 36 | // RxJS 37 | require('rxjs/Rx'); 38 | 39 | var testing = require('@angular/core/testing'); 40 | var browser = require('@angular/platform-browser-dynamic/testing'); 41 | 42 | testing.getTestBed().initTestEnvironment( 43 | browser.BrowserDynamicTestingModule, 44 | browser.platformBrowserDynamicTesting() 45 | ); 46 | 47 | Object.assign(global, testing); 48 | 49 | // var SkyPagesModule = require('../src/app/sky-pages.module'); 50 | 51 | // // console.log(SkyPagesModule); 52 | 53 | // testing.getTestBed().configureTestingModule({ 54 | // imports: [ 55 | // SkyPagesModule 56 | // ] 57 | // }); 58 | 59 | /* 60 | * Ok, this is kinda crazy. We can use the the context method on 61 | * require that webpack created in order to tell webpack 62 | * what files we actually want to require or import. 63 | * Below, context will be an function/object with file names as keys. 64 | * using that regex we are saying look in ./src/app and ./test then find 65 | * any file that ends with spec.js and get its path. By passing in true 66 | * we say do this recursively 67 | */ 68 | var testContext = skyPagesConfig.runtime.command === 'pact' ? 69 | require.context(ROOT_DIR, true, /\.pact-spec\.ts/) : 70 | require.context(ROOT_DIR, true, /\.spec\.ts/); 71 | 72 | /* 73 | * get all the files, for each file, call the context function 74 | * that will require the file and load it up here. Context will 75 | * loop and require those spec files here 76 | */ 77 | function requireAll(requireContext) { 78 | return requireContext.keys().map(requireContext); 79 | } 80 | 81 | // requires and returns all modules that match 82 | requireAll(testContext); 83 | -------------------------------------------------------------------------------- /utils/spec-styles.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true, jasmine: true */ 2 | 3 | 'use strict'; 4 | 5 | const styleLoader = require('@skyux/theme/utils/node-js/style-loader'); 6 | 7 | // A race condition exists in Firefox where tests can begin before styles are loaded. 8 | // This will ensure that styles are loaded before tests run by ensuring the style rule 9 | // for the HTML hidden property defined in sky.scss has been applied. 10 | (function () { 11 | beforeAll(function (done) { 12 | styleLoader.loadStyles().then(done); 13 | }); 14 | }()); 15 | --------------------------------------------------------------------------------