├── .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 | [](https://www.npmjs.com/package/@blackbaud/skyux-builder)
4 | [](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}${tag}>`, '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 |
--------------------------------------------------------------------------------