40 | );
41 | }
42 | }
43 |
44 | export default Switch;
45 |
--------------------------------------------------------------------------------
/src/tests/node_modules/test/utils/tests.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | *
5 | * Specific tests for reuse
6 | */
7 |
8 | /**
9 | * Extract comparable function text with code instrumentation considerations.
10 | */
11 | function comparableFunctionText (functionText) {
12 | return functionText
13 | // remove any coverage statements
14 | .replace(/[^;]+(?:cov_[^;]+);/g, '')
15 | // remove whitespace
16 | .replace(/\s+/g, '');
17 | }
18 |
19 | export function testTransform (expect, actual, expected) {
20 | Object.keys(expected).forEach((key) => {
21 | expect(actual[key].page).to.eql(expected[key].page);
22 | expect(actual[key].path).to.eql(expected[key].path);
23 | expect(actual[key].method).to.eql(expected[key].method);
24 | expect(actual[key].label).to.eql(expected[key].label);
25 |
26 | const expectedActionContents =
27 | comparableFunctionText(/\{([^}]+)\}/.exec(''+expected[key].action)[1]);
28 |
29 | expect(expectedActionContents).to.exist;
30 | expect(expectedActionContents).to.not.be.empty;
31 |
32 | expect(actual[key].action).to.be.a('function');
33 | expect(actual[key].action.length).to.eql(expected[key].action.length);
34 |
35 | expect(comparableFunctionText(''+actual[key].action))
36 | .to.contain(expectedActionContents);
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/src/application/components/header/_logo.scss:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
2 | // Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
3 | //
4 | // logo styles
5 | // =========================
6 |
7 | .logo {
8 | @include vertical-block(center, shrink) {
9 | overflow-y: visible;
10 | overflow-x: visible;
11 | align-items: center;
12 | }
13 | margin: 0 2rem 0.8rem;
14 |
15 | @include breakpoint(medium) {
16 | margin: 0 2rem 1.5rem;
17 | }
18 | @media #{$height-constrained-phone} {
19 | margin: 0 1.2rem 0.5rem;
20 | }
21 | }
22 |
23 | .logo h1 {
24 | padding: 0.3em 2.2em 1em 0em;
25 | margin: 0;
26 |
27 | font-size: 1.8em;
28 |
29 | background: none, inline-image("logo.svg", "image/svg+xml") no-repeat 100% 80%;
30 |
31 | @include breakpoint(medium) {
32 | font-size: 3em;
33 | }
34 | @media #{$height-constrained-phone} {
35 | font-size: 1.4em;
36 | }
37 | }
38 |
39 | .logo .tagline {
40 | @include grid-content;
41 | overflow-y: visible;
42 |
43 | padding-left: 0;
44 | position: relative;
45 | margin-top: 1.2 * $rem-base * $base-line-height * -1;
46 |
47 | max-width: 80%;
48 | font-size: 85%;
49 | @include breakpoint(medium) {
50 | font-size: 100%;
51 | margin-top: 2 * $rem-base * $base-line-height * -1;
52 | }
53 | @media #{$height-constrained-phone} {
54 | margin-top: $rem-base * $base-line-height * -1;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/build/fixtures.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | /* global Promise */
6 | import fs from 'fs';
7 | import path from 'path';
8 | import utils from 'utils/node';
9 |
10 | /**
11 | * Factory for the fixtures task.
12 | * Runs each test fixture generator in generatorsDir in parallel.
13 | * Each fixtures generator is assumed to export a node style async function.
14 | *
15 | * @param {Object} settings - The project settings.
16 | * @returns {Function} The fixtures task.
17 | */
18 | export default function fixturesTaskFactory (settings) {
19 | const generatorsDir = `./${settings.src.tests}/generators`;
20 | const options = {
21 | // 'script-filename.js': {}
22 | };
23 |
24 | return function fixtures () {
25 | return utils.nodeCall(fs.readdir, generatorsDir).then((generators) => {
26 | return Promise
27 | .all(generators.map((generatorScript) => {
28 | const generator = path.resolve('.', generatorsDir, generatorScript);
29 | return utils.nodeCall(
30 | require(generator).run,
31 | options[generatorScript]
32 | );
33 | }))
34 | .catch((error) => {
35 | throw new Error(`fixtures task failed: ${error}`);
36 | })
37 | }).catch((error) => {
38 | throw new Error(`fixtures task failed: ${error}`);
39 | });
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/src/application/client/sw/assets.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | *
5 | * Precaching and route installs for non-project (cdn) assets.
6 | * The 'sw/data' module is generated by the build @see src/build/service-worker.
7 | */
8 | import toolbox from 'sw-toolbox';
9 | import urlm from 'utils/urls';
10 | import data from 'sw/data';
11 |
12 | /**
13 | * Install route GET handlers for CDN requests and precache assets.
14 | *
15 | * Route handlers for CDN requests are installed everytime as a side effect
16 | * of setting up precaching. However, precaching is only carried out as a result
17 | * of an 'install' event (not everytime).
18 | *
19 | * @see sw-toolbox
20 | */
21 | export function setupAssetRequests () {
22 | let hostname;
23 |
24 | toolbox.precache(
25 | data.assets
26 | .sort()
27 | .map(function (asset) {
28 | const next = urlm.getHostname(asset);
29 |
30 | if (hostname !== next) {
31 | hostname = next;
32 | // New hostname, so install GET handler for that host
33 | toolbox.router.get('*', toolbox.networkFirst, {
34 | origin: hostname,
35 | // any/all CDNs get 3 seconds max
36 | networkTimeoutSeconds: 3
37 | });
38 | }
39 |
40 | // Precache the asset in 'install'
41 | return asset;
42 | })
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/src/application/server/statics.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | *
5 | * static asset serving middleware.
6 | */
7 | import glob from 'glob';
8 | import express from 'express';
9 |
10 | /**
11 | * Use simple static with far future expires.
12 | * If not found, check to see if a distinct, different version exists that would
13 | * satisfy the request. If so, serve that.
14 | * Expects to be mounted on settings.web.baseDir.
15 | *
16 | * @param {Object} settings - The application config settings.
17 | * @returns {Function} serveStatic middleware.
18 | */
19 | export default function statics (settings) {
20 | const serveStatic = express.static(
21 | settings.dist.baseDir, { maxAge: settings.web.assetAge }
22 | );
23 |
24 | return function serveStaticAsset (req, res, next) {
25 | serveStatic(req, res, (err) => {
26 | if (err) {
27 | return next(err);
28 | }
29 | // If a newer version exists, rewrite and serve that.
30 | const urlMatch = req.url.replace(/[a-f0-9]+\./, '*.');
31 | glob(settings.dist.baseDir + urlMatch, {
32 | silent: true
33 | }, (matchError, matches) => {
34 | if (matchError || matches.length !== 1) {
35 | return next();
36 | }
37 | req.url = matches[0].replace(settings.dist.baseDir, '');
38 | return serveStatic(req, res, next);
39 | });
40 | });
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/src/tests/node_modules/test/mocks/subscription.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | *
5 | * Mock responses for the subscription service.
6 | */
7 | 'use strict';
8 |
9 | var allTopics = [{
10 | label: 'Alerts',
11 | tag: 'push-alerts-tag'
12 | }, {
13 | label: 'Upcoming Events',
14 | tag: 'push-upcoming-events-tag'
15 | }];
16 |
17 | var updateTopic = [{
18 | label: 'Alerts',
19 | tag: 'push-alerts-tag',
20 | subscribe: true
21 | }];
22 |
23 | function mockError (params) {
24 | var err;
25 | if (params.emulateError) {
26 | err = new Error('mock service error');
27 | }
28 | return err;
29 | }
30 |
31 | function unsubscribe (params, config, callback) {
32 | var err = mockError(params);
33 | callback(err);
34 | }
35 |
36 | module.exports = {
37 | updateTopic: updateTopic,
38 | topics: allTopics,
39 | read: function read (params, config, callback) {
40 | var err = mockError(params);
41 | callback(err, allTopics);
42 | },
43 | create: function create (params, body, config, callback) {
44 | var err = mockError(params);
45 | callback(err, allTopics);
46 | },
47 | update: function update (params, body, config, callback) {
48 | var err = mockError(params);
49 | // just send update back
50 | callback(err, body.topics);
51 | },
52 | // This is 'del' because of fluxible-plugin-fetchr/utils/MockServiceManager
53 | del: unsubscribe,
54 | delete: unsubscribe
55 | };
56 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 | on:
3 | pull_request:
4 | types: [closed]
5 | branches:
6 | - 'release**'
7 | - 'stage**'
8 |
9 | jobs:
10 | deploy:
11 | runs-on: ubuntu-latest
12 | env:
13 | DEPLOY_URL: ${{ (contains(github.ref, 'release') && 'https://pwa.localnerve.net') || 'https://pwa-stage.localnerve.net' }}
14 | APP_NAME: ${{ (contains(github.ref, 'release') && 'pwa') || 'pwa-stage' }}
15 |
16 | if: github.event.pull_request.merged
17 | steps:
18 | - name: Get Branch Name
19 | id: get_branch
20 | run: echo ::set-output name=BRANCH_NAME::${GITHUB_REF/refs\/heads\//}
21 | - uses: actions/checkout@v3
22 | - name: Setup Node
23 | uses: actions/setup-node@v3.0.0
24 | with:
25 | node-version: '12'
26 | - name: Echo Input
27 | run: |
28 | echo DEPLOY_URL=$DEPLOY_URL
29 | echo APP_NAME=$APP_NAME
30 | - name: Install Dependencies
31 | run: npm install
32 | - name: Deploy to Heroku
33 | if: ${{ success() }}
34 | uses: akhileshns/heroku-deploy@v3.12.12
35 | with:
36 | heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
37 | heroku_app_name: ${{ env.APP_NAME }}
38 | heroku_email: 'alex@localnerve.com'
39 | branch: ${{ steps.get_branch.outputs.BRANCH_NAME }}
40 | healthcheck: ${{ format('https://{0}.herokuapp.com', env.APP_NAME) }}
41 | delay: 5
42 | - name: Post-deploy
43 | if: ${{ success() }}
44 | run: |
45 | echo GET '$DEPLOY_URL'
46 | curl -i $DEPLOY_URL
47 |
--------------------------------------------------------------------------------
/src/application/server/services/data/index.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | import debugLib from 'debug';
6 | import cache from './cache-interface';
7 | import fetchLib from './fetch';
8 |
9 | const debug = debugLib('services:data:index');
10 |
11 | /**
12 | * Get a resource from cache.
13 | * If not found, get a resource from FRED
14 | *
15 | * @param {Object} params - The parameters controlling fetch.
16 | * @param {String} params.resource - The name of the resource to fetch.
17 | * @param {String} [params.url] - The url of the resource to fetch.
18 | * Not required if expected in cache.
19 | * @param {Function} callback - The callback to execute on completion.
20 | */
21 | export function fetch (params, callback) {
22 | debug(`fetching resource "${params.resource}"`);
23 |
24 | const resource = cache.get(params.resource);
25 |
26 | if (resource) {
27 | debug('cache hit');
28 | return callback(null, resource);
29 | }
30 |
31 | // If a cache hit was required, see if we already have a fetchable spec.
32 | if (!fetchLib.isManifestRequest(params) && !params.url) {
33 | const spec = cache.find(params.resource);
34 | if (spec) {
35 | params = spec;
36 | }
37 | }
38 |
39 | fetchLib.fetchOne(params, callback);
40 | }
41 |
42 | export const initialize = fetchLib.fetchMain;
43 | export const update = fetchLib.fetchAll;
44 |
45 | export default {
46 | fetch,
47 | initialize,
48 | update
49 | };
50 |
--------------------------------------------------------------------------------
/src/application/components/header/Nav.jsx:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | import React from 'react';
6 | import PropTypes from 'prop-types';
7 | import { NavLink } from 'fluxible-router';
8 | import sizeAction from 'application/actions/size';
9 | import { fluxibleWindowResizeReporter } from 'react-element-size-reporter';
10 | import cx from 'classnames';
11 |
12 | class Nav extends React.Component {
13 | static get propTypes () {
14 | return {
15 | selected: PropTypes.string.isRequired,
16 | links: PropTypes.array.isRequired
17 | };
18 | }
19 |
20 | render () {
21 | const selected = this.props.selected,
22 | links = this.props.links,
23 | linkHTML = links.map(function (link) {
24 | return (
25 |
29 | {link.label}
30 |
31 | );
32 | });
33 | return (
34 |
35 | {linkHTML}
36 |
37 | );
38 | }
39 | }
40 |
41 | const nav = fluxibleWindowResizeReporter(Nav, '.navigation', sizeAction, {
42 | resizeWait: 50,
43 | sizeReporter: {
44 | reportTop: true,
45 | reportHeight: true,
46 | grow: {
47 | top: 5,
48 | height: 10
49 | }
50 | }
51 | });
52 |
53 | export default nav;
54 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are met:
5 |
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 |
9 | * Redistributions in binary form must reproduce the above copyright
10 | notice, this list of conditions and the following disclaimer in the
11 | documentation and/or other materials provided with the distribution.
12 |
13 | * Neither the name of the LocalNerve, LLC nor the
14 | names of its contributors may be used to endorse or promote products
15 | derived from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL LocalNerve, LLC BE LIABLE FOR ANY
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/src/application/components/pages/contact/Input.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | import React from 'react';
6 |
7 | import PropTypes from 'prop-types';
8 |
9 | class ContactInput extends React.Component {
10 | static get propTypes () {
11 | return {
12 | fieldValue: PropTypes.string,
13 | setInputReference: PropTypes.func.isRequired,
14 | label: PropTypes.object.isRequired,
15 | inputElement: PropTypes.string.isRequired,
16 | inputType: PropTypes.string,
17 | inputId: PropTypes.string.isRequired,
18 | focus: PropTypes.bool.isRequired
19 | };
20 | }
21 |
22 | render () {
23 | const inputElement = React.createElement(this.props.inputElement, {
24 | type: this.props.inputType,
25 | id: this.props.inputId,
26 | name: this.props.inputId,
27 | key: this.props.inputId,
28 | title: this.props.label.help,
29 | placeholder: this.props.label.help,
30 | ref: this.props.setInputReference,
31 | className: 'form-value-element',
32 | autoFocus: this.props.focus,
33 | required: true,
34 | 'aria-required': true,
35 | defaultValue: this.props.fieldValue
36 | });
37 | return (
38 |
39 |
42 | {inputElement}
43 |
44 | );
45 | }
46 | }
47 |
48 | export default ContactInput;
49 |
--------------------------------------------------------------------------------
/src/tests/node_modules/test/fixtures/models-response.js:
--------------------------------------------------------------------------------
1 | /** This is a generated file **/
2 | /**
3 | GENERATION_TIME = Tue May 17 2016 23:33:02 GMT-0400 (EDT)
4 | NODE_ENV = development
5 | FRED_URL = https://api.github.com/repos/localnerve/fred/contents/resources.json?ref=development
6 | **/
7 | /*eslint quotes:0 */
8 | module.exports = JSON.parse(JSON.stringify(
9 | {"LocalBusiness":{"legalName":"LocalNerve, LLC","alternateName":"LocalNerve","url":"http://localnerve.com","telephone":"207-370-8005","email":"alex@localnerve.com","address":{"streetAddress":"PO BOX 95","addressRegion":"ME","addressLocality":"Windham","addressCountry":"USA","postalCode":"04062","postOfficeBoxNumber":"95"}},"SiteInfo":{"site":{"name":"Contactor","tagLine":"A Fluxible, Reactive reference app with a good prognosis.","bullets":["Fluxible","React","Data Driven"]},"license":{"type":"BSD","url":"https://github.com/localnerve/react-pwa-reference/blob/master/LICENSE.md","statement":"All code licensed under LocalNerve BSD License."},"developer":{"name":"LocalNerve","byLine":"Developed by LocalNerve","url":"http://localnerve.com"},"social":{"github":"https://github.com/localnerve/react-pwa-reference","twitter":"https://twitter.com/localnerve","facebook":"https://facebook.com/localnerve","linkedin":"https://www.linkedin.com/in/alexpaulgrant","googleplus":"https://plus.google.com/118303375063449115817/"}},"Settings":{"component":"settings","split":"settings","resource":"settings","url":"https://api.github.com/repos/localnerve/fred/contents/pages/settings.json","format":"json","action":{"name":"settings","params":{}},"models":["LocalBusiness","SiteInfo","Settings"]}}
10 | ));
--------------------------------------------------------------------------------
/src/application/components/_app.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | // Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | //
5 | // Components styles
6 | //
7 | .app-frame {
8 | @include grid-frame(vertical);
9 | // If viewport height less than 728 AND landscape, float the footer
10 | // under the content. Otherwise height is 100vh, and footer sticks to viewport
11 | // bottom.
12 | @media only screen and (orientation: landscape) and (max-height: 727px) {
13 | height: auto;
14 | }
15 | }
16 | .app-block {
17 | @include vertical-block(left);
18 | }
19 |
20 | .app-bg {
21 | position: absolute;
22 | // assigned in js
23 | // top: 0;
24 | right:0;
25 | bottom: 0;
26 | left: 0;
27 | width: 100%;
28 | // assigned in js
29 | // height: 100%;
30 |
31 | background-color: transparent;
32 | background-repeat: no-repeat;
33 |
34 | // assigned in js
35 | // opacity
36 |
37 | transition: opacity 0.4s ease;
38 | }
39 |
40 | .page {
41 | @include vertical-block(spaced);
42 | }
43 |
44 | .swipe-container {
45 | // Allow vertical content scrolling for longer content/constrained height
46 | overflow-y: auto !important;
47 | }
48 |
49 | a, a:visited {
50 | color: $app-primary-light-color;
51 | text-decoration: none;
52 | outline: 0;
53 | }
54 | a:hover {
55 | color: darken($app-primary-light-color, 10%);
56 | }
57 |
58 | %header-footer-bg {
59 | background: $app-primary-bgcolor;
60 | }
61 |
62 | @import "header/styles";
63 | @import "pages/styles";
64 | @import "footer/styles";
65 | @import "react-modal";
66 | @import "notification";
67 |
--------------------------------------------------------------------------------
/src/application/components/pages/settings/_topics.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | // Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | //
5 | // Topics styles
6 | //
7 |
8 | .topics-list {
9 | list-style: none;
10 |
11 | .topic-box > input {
12 | position: absolute;
13 | left: -9999px;
14 | outline: none;
15 | }
16 | .topic-box {
17 | display: inline-block;
18 | position: relative;
19 | vertical-align: middle;
20 | width: 2rem;
21 | height: 2rem;
22 | margin: 0.5rem auto;
23 |
24 | input[type="checkbox"]:checked + label:after {
25 | opacity: 1;
26 | }
27 |
28 | input[type="checkbox"]:disabled + label,
29 | input[type="checkbox"]:disabled + label:after {
30 | background: #ccc;
31 | }
32 |
33 | label {
34 | cursor: pointer;
35 | position: absolute;
36 | width: 2rem;
37 | height: 2rem;
38 | top: 0;
39 | border-radius: 0.25rem;
40 | background: $app-primary-bgcolor;
41 |
42 | &::after {
43 | position: absolute;
44 | content: '';
45 | opacity: 0;
46 | width: 0.9rem;
47 | height: 0.5rem;
48 | background: transparent;
49 | top: 0.6rem;
50 | left: 0.6rem;
51 | border: 3px solid #fcfff4;
52 | border-top: none;
53 | border-right: none;
54 | transform: rotate(-45deg);
55 | }
56 | &:hover::after {
57 | opacity: 0.3;
58 | }
59 | }
60 | }
61 | .topic-label {
62 | display: inline-block;
63 | padding-left: 0.5rem;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/tests/unit/services/error.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | /* global describe, before, it */
6 | 'use strict';
7 |
8 | var expect = require('chai').expect;
9 |
10 | describe('error decorator', function () {
11 | var error;
12 |
13 | before(function () {
14 | error = require('application/server/services/error').decorateFetchrError;
15 | });
16 |
17 | it('should pass through falsy error', function () {
18 | expect(error(false)).to.be.false;
19 | expect(error()).to.be.undefined;
20 | expect(error(null)).to.be.null;
21 | expect(error('')).to.be.a('string').that.is.empty;
22 | expect(error(0)).to.equal(0);
23 | expect(isNaN(error(NaN))).to.be.true;
24 | });
25 |
26 | it('should convert string to Error', function () {
27 | expect(error('this is an error')).to.be.an.instanceof(Error);
28 | });
29 |
30 | it('should decorate an object with Fetchr requirements', function () {
31 | var decoratedErr = error({});
32 |
33 | expect(decoratedErr).to.have.property('statusCode');
34 | expect(decoratedErr).to.have.property('output');
35 | });
36 |
37 | it('should decorate an Error with Fetchr requirements', function () {
38 | var err = new Error('this is an error');
39 | var decoratedErr = error(err);
40 |
41 | expect(decoratedErr).to.have.property('statusCode');
42 | expect(decoratedErr).to.have.property('output');
43 | expect(decoratedErr.output).to.have.property('message');
44 | expect(decoratedErr.output.message).to.equal(err.message);
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/src/node_modules/configs/images/index.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | *
5 | * Environment specific configuration for images.
6 | *
7 | * Environment variables:
8 | * IMAGE_SERVICE_URL - A string that denotes an image service, default lorempixel.
9 | * CLOUD_NAME - A string that denotes a cloud name for use in an image service.
10 | */
11 | 'use strict';
12 |
13 | /**
14 | * Get the IMAGE_SERVICE_URL configuration value.
15 | * Defaults to lorempixel if IMAGE_SERVICE_URL is not defined.
16 | * Lorempixel does not maintain an SSL certificate properly.
17 | * image service must be SSL for use with this app (except development).
18 | * Note: To use Cloudinary, set IMAGE_SERVICE_URL to 'https://res.cloudinary.com'
19 | * AND set CLOUD_NAME appropriately.
20 | *
21 | * @returns {String} The IMAGE_SERVICE_URL configuration value.
22 | */
23 | function IMAGE_SERVICE_URL () {
24 | return process.env.IMAGE_SERVICE_URL || 'http://lorempixel.com';
25 | }
26 |
27 | /**
28 | * Get the CLOUD_NAME configuration value.
29 | * This is used in Cloudinary to identify the account.
30 | *
31 | * @returns {String} The CLOUD_NAME configuration value.
32 | */
33 | function CLOUD_NAME () {
34 | return process.env.CLOUD_NAME;
35 | }
36 |
37 | /**
38 | * Make the images configuration object.
39 | *
40 | * @returns the images configuration object.
41 | */
42 | function makeConfig () {
43 | return {
44 | service: {
45 | url: IMAGE_SERVICE_URL,
46 | cloudName: CLOUD_NAME
47 | }
48 | };
49 | }
50 |
51 | module.exports = makeConfig;
52 |
--------------------------------------------------------------------------------
/src/tests/node_modules/test/fixtures/404-response.js:
--------------------------------------------------------------------------------
1 | /** This is a generated file **/
2 | /**
3 | GENERATION_TIME = Tue May 17 2016 23:33:02 GMT-0400 (EDT)
4 | NODE_ENV = development
5 | FRED_URL = https://api.github.com/repos/localnerve/fred/contents/resources.json?ref=development
6 | **/
7 | /*eslint quotes:0 */
8 | module.exports = JSON.parse(JSON.stringify(
9 | {"models":{"LocalBusiness":{"legalName":"LocalNerve, LLC","alternateName":"LocalNerve","url":"http://localnerve.com","telephone":"207-370-8005","email":"alex@localnerve.com","address":{"streetAddress":"PO BOX 95","addressRegion":"ME","addressLocality":"Windham","addressCountry":"USA","postalCode":"04062","postOfficeBoxNumber":"95"}},"SiteInfo":{"site":{"name":"Contactor","tagLine":"A Fluxible, Reactive reference app with a good prognosis.","bullets":["Fluxible","React","Data Driven"]},"license":{"type":"BSD","url":"https://github.com/localnerve/react-pwa-reference/blob/master/LICENSE.md","statement":"All code licensed under LocalNerve BSD License."},"developer":{"name":"LocalNerve","byLine":"Developed by LocalNerve","url":"http://localnerve.com"},"social":{"github":"https://github.com/localnerve/react-pwa-reference","twitter":"https://twitter.com/localnerve","facebook":"https://facebook.com/localnerve","linkedin":"https://www.linkedin.com/in/alexpaulgrant","googleplus":"https://plus.google.com/118303375063449115817/"}},"Settings":{"component":"settings","split":"settings","resource":"settings","url":"https://api.github.com/repos/localnerve/fred/contents/pages/settings.json","format":"json","action":{"name":"settings","params":{}},"models":["LocalBusiness","SiteInfo","Settings"]}},"content":"
Not Found
\n
Sorry, but the page you are trying to view does not exist.
\n"}
10 | ));
--------------------------------------------------------------------------------
/src/tests/unit/utils/syncable.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | /* global describe, it */
6 |
7 | import { expect } from 'chai';
8 | import syncable from 'utils/syncable';
9 |
10 | describe('syncable', () => {
11 | it('should expose expected operations', () => {
12 | expect(syncable).to.respondTo('push');
13 | expect(syncable).to.respondTo('contact');
14 | expect(syncable).to.have.property('ops').that.is.an('object')
15 | .that.is.not.empty;
16 | expect(syncable).to.have.property('types').that.is.an('object')
17 | .that.is.not.empty;
18 | expect(syncable).to.have.property('propertyName').that.is.a('string')
19 | .that.is.not.empty;
20 | });
21 |
22 | describe('push', () => {
23 | it('should do nothing for a bad input', () => {
24 | expect(syncable.push(null)).to.be.null;
25 | });
26 |
27 | it('should create a fallback property for push', () => {
28 | const test = {};
29 | const result = syncable.push(test);
30 |
31 | expect(result._fallback).to.have.property('type');
32 | expect(result._fallback.type).to.equal('push');
33 | });
34 | });
35 |
36 | describe('contact', () => {
37 | it('should do nothing for a bad input', () => {
38 | expect(syncable.contact(null)).to.be.null;
39 | });
40 |
41 | it('should create a fallback property for contact', () => {
42 | const test = {};
43 | const result = syncable.contact(test);
44 |
45 | expect(result._fallback).to.have.property('type');
46 | expect(result._fallback.type).to.equal('contact');
47 | });
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/src/node_modules/configs/analytics/index.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | *
5 | * Environment variables can override the following:
6 | * ANALYTICS_ID - The analytics id used in the trackingTemplate.
7 | */
8 | /* jshint multistr: true */
9 | 'use strict';
10 |
11 | var uaID = {
12 | development: 'UA-XXXXXXXX-D',
13 | production: 'UA-XXXXXXXX-P'
14 | };
15 |
16 | var uaRef = 'ga';
17 |
18 | var trackingTemplate = '(function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){ \
19 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), \
20 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) \
21 | })(window,document,"script","//www.google-analytics.com/analytics.js","__UAREF__"); \
22 | __UAREF__("create", "__UAID__", "auto"); \
23 | __UAREF__("send", "pageview");';
24 |
25 | /**
26 | * Get the analytics id.
27 | *
28 | * @param {String} env - The node environment
29 | * @access private
30 | * @returns {String} The analytics id for use in the trackingTemplate.
31 | */
32 | function UAID (env) {
33 | return process.env.ANALYTICS_ID || uaID[env];
34 | }
35 |
36 | /**
37 | * Make the analytics configuration object.
38 | *
39 | * @param {Object} nconf - The nconfig object
40 | * @returns {Object} The analytics configuration object.
41 | */
42 | function makeConfig (nconf) {
43 | var env = nconf.get('NODE_ENV');
44 |
45 | return {
46 | snippet: trackingTemplate
47 | .replace(/__UAID__/g, UAID(env))
48 | .replace(/__UAREF__/g, uaRef),
49 | globalRef: uaRef
50 | };
51 | }
52 |
53 | module.exports = makeConfig;
54 |
--------------------------------------------------------------------------------
/src/node_modules/utils/urls.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 |
6 | /**
7 | * Given a url, extract the hostname part.
8 | *
9 | * @param {String} url - The url from which to pull the hostname.
10 | * @returns {String} The hostname from the given url.
11 | */
12 | export function getHostname (url) {
13 | return url.replace(/^[^/]*\/\/([^/?#:]+).*$/, (all, hostname) => hostname);
14 | }
15 |
16 | /**
17 | * Given a url, extract the last path segment.
18 | * Last path segment is the file name, or file, or any last part of the path
19 | * before ?|#|$
20 | *
21 | * @param {String} url - The url from which to pull the last path segment.
22 | * Can be absolute or relative url, path, file, or path and qs/hash
23 | * @returns {String} The last path segment
24 | */
25 | export function getLastPathSegment (url) {
26 | const matches = /(?:\/{1}|^)([\w\-.]+)\/?(?=\?|#|$)/.exec(url);
27 | return matches && matches[1] || '';
28 | }
29 |
30 | /**
31 | * The significant hostname is the last hostname token before the TLD.
32 | * https://subdom.significant-hostname.com/someotherstuff
33 | *
34 | * @param {String} url - The url from which to pull the significant hostname.
35 | * @returns The second to last hostname token between dots for a given url.
36 | */
37 | export function getSignificantHostname (url) {
38 | const hostname = getHostname(url);
39 | const names = hostname.split('.');
40 | const significantIndex = names.length < 2 ? 0 : names.length - 2;
41 | return names[significantIndex];
42 | }
43 |
44 | export default {
45 | getHostname,
46 | getSignificantHostname,
47 | getLastPathSegment
48 | };
49 |
--------------------------------------------------------------------------------
/src/application/stores/RouteStore.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | *
5 | * Extend the fluxible route store so routes can have action functions.
6 | * Just de/rehydate the functions.
7 | */
8 | import { RouteStore as FluxibleRouteStore } from 'fluxible-router';
9 | import inherits from 'inherits';
10 | import { createFluxibleRouteTransformer } from 'utils';
11 | import actionsInterface from 'application/actions/interface';
12 |
13 | const transformer = createFluxibleRouteTransformer({
14 | actions: actionsInterface.getActions()
15 | });
16 |
17 | /**
18 | * Creates a RouteStore.
19 | *
20 | * @class
21 | */
22 | export function RouteStore () {
23 | FluxibleRouteStore.apply(this, arguments);
24 | }
25 |
26 | inherits(RouteStore, FluxibleRouteStore);
27 |
28 | RouteStore.storeName = FluxibleRouteStore.storeName;
29 | RouteStore.handlers = FluxibleRouteStore.handlers;
30 |
31 | /**
32 | * Dehydrates this object to state.
33 | * Transforms routes to json.
34 | *
35 | * @returns {Object} The RouteStore represented as state.
36 | */
37 | RouteStore.prototype.dehydrate = function dehydrate () {
38 | const state = FluxibleRouteStore.prototype.dehydrate.apply(this, arguments);
39 | state.routes = transformer.fluxibleToJson(state.routes);
40 | return state;
41 | };
42 |
43 | /**
44 | * Rehydrates this object from state.
45 | * Creates routes from json using transformer.
46 | */
47 | RouteStore.prototype.rehydrate = function rehydrate (state) {
48 | state.routes = transformer.jsonToFluxible(state.routes);
49 | return FluxibleRouteStore.prototype.rehydrate.apply(this, arguments);
50 | };
51 |
52 | export default RouteStore;
53 |
--------------------------------------------------------------------------------
/src/tests/functional/run-parallel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | *
5 | * A node command line program to run mocha in parallel per browser spec.
6 | * Relies on global mocha package
7 | */
8 | /*eslint no-console:0 */
9 | /* global Promise */
10 | 'use strict';
11 |
12 | var exec = require('child_process').exec;
13 | var browserSpecs = require('./browsers');
14 |
15 | var mochaArgs = process.argv[2];
16 | var baseUrl = process.argv[3];
17 |
18 | var browsers = Object.keys(browserSpecs);
19 |
20 | // context specific log
21 | function log (config, data) {
22 | config = (config + ' ').slice(0, 10);
23 | ('' + data).split(/(\r?\n)/g).forEach(function(line) {
24 | if (line.replace(/\033\[[0-9;]*m/g,'').trim().length >0) {
25 | console.log(config + ': ' + line.trimRight() );
26 | }
27 | });
28 | }
29 |
30 | // Run a mocha test for a given browser
31 | function runMocha (browser, baseUrl, done) {
32 | var env = JSON.parse(JSON.stringify(process.env));
33 | env.TEST_BROWSER = browser;
34 | env.TEST_BASEURL = baseUrl;
35 |
36 | var mocha = exec('mocha ' + mochaArgs, {
37 | env: env
38 | }, done);
39 |
40 | mocha.stdout.on('data', log.bind(null, browser));
41 | mocha.stderr.on('data', log.bind(null, browser));
42 | }
43 |
44 | Promise
45 | .all(browsers.map(function (browser) {
46 | return new Promise(function (resolve, reject) {
47 | runMocha(browser, baseUrl, function (err) {
48 | if (err) {
49 | return reject(err);
50 | }
51 | resolve();
52 | });
53 | });
54 | }))
55 | .then(function() {
56 | console.log('ALL TESTS SUCCESSFUL');
57 | });
58 |
--------------------------------------------------------------------------------
/src/build/webpack/swReg.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | import makeMode from './utils/mode';
6 | import uglifyPluginFactory from './plugins/uglify';
7 | import statsPluginFactory from './plugins/stats';
8 |
9 | /**
10 | * Generate the webpack config for the service worker registration bundle.
11 | *
12 | * @param {Object} settings - The project settings.
13 | * @param {String} type - One of ['dev', 'prod', 'perf'].
14 | * @returns {Object} The web pack config for the sw reg bundle.
15 | */
16 | export default function swRegConfig (settings, type) {
17 | const devtoolModuleFilenameTemplate = 'webpack:///sw-reg/[resource-path]';
18 |
19 | const config = {
20 | mode: makeMode(type),
21 | entry: {
22 | swReg: `./${settings.src.serviceWorker.registration}`
23 | },
24 | output: {
25 | path: settings.webpack.absoluteOutputPath,
26 | filename: type === 'prod' ? '[name].[chunkhash].min.js' : '[name].js',
27 | publicPath: settings.web.scripts,
28 | devtoolModuleFilenameTemplate: type === 'prod'
29 | ? undefined : devtoolModuleFilenameTemplate
30 | },
31 | module: {
32 | rules: [
33 | {
34 | test: /\.js$/,
35 | exclude: /^\/node_modules/,
36 | loader: 'babel-loader'
37 | }
38 | ]
39 | },
40 | devtool: type === 'prod' ? undefined : 'source-map',
41 | stats: 'verbose',
42 | plugins: [
43 | statsPluginFactory(settings, false)
44 | ],
45 | optimization: type === 'prod' ? {
46 | minimizer: [
47 | uglifyPluginFactory()
48 | ]
49 | } : undefined
50 | };
51 |
52 | return config;
53 | }
54 |
--------------------------------------------------------------------------------
/src/tests/node_modules/test/mocks/requestLib.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | 'use strict';
6 |
7 | var routesResponse = require('test/fixtures/routes-response');
8 | var config = require('configs').create().data;
9 |
10 | // setup some canned responses
11 | var defaultResponse = 'aGVsbG8gd29ybGQK'; // base64 encoded 'hello world'
12 | var responses = {};
13 | // base64 encode routes-response fixture as response for FRED.url
14 | responses[config.FRED.url()] =
15 | new Buffer(JSON.stringify(routesResponse)).toString(config.FRED.contentEncoding());
16 |
17 | function Request () {
18 | this.__options = {};
19 | }
20 |
21 | Request.prototype = {
22 | get: function get (options, cb) {
23 | var body;
24 | var incomingMessage;
25 | var url = options.url || options.uri;
26 |
27 | // echo options for usage audit
28 | this.__options = options;
29 |
30 | // The client should not be relying on the incomingMessage if error.
31 | if (!this.emulateError) {
32 | incomingMessage = {
33 | statusCode: 200
34 | };
35 | }
36 |
37 | if (!this.noData) {
38 | body = {
39 | content: responses[url] || defaultResponse
40 | };
41 | }
42 |
43 | cb(
44 | this.emulateError ? new Error('mock error') : null,
45 | incomingMessage,
46 | body
47 | );
48 | },
49 | /***
50 | * Mock control and audit only
51 | */
52 | get url () {
53 | return this.__options.url;
54 | },
55 | setEmulateError: function (value) {
56 | this.emulateError = value;
57 | },
58 | setNoData: function (value) {
59 | this.noData = value;
60 | }
61 | };
62 |
63 | module.exports = new Request();
64 |
--------------------------------------------------------------------------------
/src/tests/node_modules/test/fixtures/500-response.js:
--------------------------------------------------------------------------------
1 | /** This is a generated file **/
2 | /**
3 | GENERATION_TIME = Tue May 17 2016 23:33:02 GMT-0400 (EDT)
4 | NODE_ENV = development
5 | FRED_URL = https://api.github.com/repos/localnerve/fred/contents/resources.json?ref=development
6 | **/
7 | /*eslint quotes:0 */
8 | module.exports = JSON.parse(JSON.stringify(
9 | {"models":{"LocalBusiness":{"legalName":"LocalNerve, LLC","alternateName":"LocalNerve","url":"http://localnerve.com","telephone":"207-370-8005","email":"alex@localnerve.com","address":{"streetAddress":"PO BOX 95","addressRegion":"ME","addressLocality":"Windham","addressCountry":"USA","postalCode":"04062","postOfficeBoxNumber":"95"}},"SiteInfo":{"site":{"name":"Contactor","tagLine":"A Fluxible, Reactive reference app with a good prognosis.","bullets":["Fluxible","React","Data Driven"]},"license":{"type":"BSD","url":"https://github.com/localnerve/react-pwa-reference/blob/master/LICENSE.md","statement":"All code licensed under LocalNerve BSD License."},"developer":{"name":"LocalNerve","byLine":"Developed by LocalNerve","url":"http://localnerve.com"},"social":{"github":"https://github.com/localnerve/react-pwa-reference","twitter":"https://twitter.com/localnerve","facebook":"https://facebook.com/localnerve","linkedin":"https://www.linkedin.com/in/alexpaulgrant","googleplus":"https://plus.google.com/118303375063449115817/"}},"Settings":{"component":"settings","split":"settings","resource":"settings","url":"https://api.github.com/repos/localnerve/fred/contents/pages/settings.json","format":"json","action":{"name":"settings","params":{}},"models":["LocalBusiness","SiteInfo","Settings"]}},"content":"
Whoops!
\n
We are experiencing some technical difficulties, possibly due to a network or service interruption.
\n
Sorry for the inconvenience, Please try again later.
61 | );
62 | }, this);
63 | }
64 | }
65 | }
66 |
67 | export default ContactSteps;
68 |
--------------------------------------------------------------------------------
/src/tests/node_modules/test/fixtures/settings-response.js:
--------------------------------------------------------------------------------
1 | /** This is a generated file **/
2 | /**
3 | GENERATION_TIME = Tue May 17 2016 23:33:02 GMT-0400 (EDT)
4 | NODE_ENV = development
5 | FRED_URL = https://api.github.com/repos/localnerve/fred/contents/resources.json?ref=development
6 | **/
7 | /*eslint quotes:0 */
8 | module.exports = JSON.parse(JSON.stringify(
9 | {"models":{"LocalBusiness":{"legalName":"LocalNerve, LLC","alternateName":"LocalNerve","url":"http://localnerve.com","telephone":"207-370-8005","email":"alex@localnerve.com","address":{"streetAddress":"PO BOX 95","addressRegion":"ME","addressLocality":"Windham","addressCountry":"USA","postalCode":"04062","postOfficeBoxNumber":"95"}},"SiteInfo":{"site":{"name":"Contactor","tagLine":"A Fluxible, Reactive reference app with a good prognosis.","bullets":["Fluxible","React","Data Driven"]},"license":{"type":"BSD","url":"https://github.com/localnerve/react-pwa-reference/blob/master/LICENSE.md","statement":"All code licensed under LocalNerve BSD License."},"developer":{"name":"LocalNerve","byLine":"Developed by LocalNerve","url":"http://localnerve.com"},"social":{"github":"https://github.com/localnerve/react-pwa-reference","twitter":"https://twitter.com/localnerve","facebook":"https://facebook.com/localnerve","linkedin":"https://www.linkedin.com/in/alexpaulgrant","googleplus":"https://plus.google.com/118303375063449115817/"}},"Settings":{"component":"settings","split":"settings","resource":"settings","url":"https://api.github.com/repos/localnerve/fred/contents/pages/settings.json","format":"json","action":{"name":"settings","params":{}},"models":["LocalBusiness","SiteInfo","Settings"]}},"content":{"name":"settings","heading":"Settings","settingsNotSupported":"Settings Not Available On This Device","pushNotifications":{"enable":"Enable Push Notifications","notificationsNotSupported":"Notifications Not Supported","notificationsBlocked":"Notifications Blocked","pushMessagingNotSupported":"Push Messaging Not Supported","topics":[{"label":"Alerts","tag":"push-alerts-tag"},{"label":"Upcoming Events","tag":"push-upcoming-events-tag"}]},"backgroundSync":{"enable":"Enable Background Sync","notSupported":"Background Sync Not Supported"},"demo":{"pushNotification":true,"backgroundSync":true}}}
10 | ));
--------------------------------------------------------------------------------
/src/tests/unit/services/data/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | /* global before, after, beforeEach, describe, it */
6 |
7 | import { expect } from 'chai';
8 | import mocks from 'test/mocks';
9 |
10 | describe('data/index', () => {
11 | let data, cache, fetchLib;
12 |
13 | before(function () {
14 | this.timeout(5000);
15 |
16 | mocks.fetch.begin();
17 | data = require('application/server/services/data');
18 | cache = require('./cache-interface');
19 | fetchLib = require('./fetch');
20 | });
21 |
22 | after(() => {
23 | mocks.fetch.end();
24 | });
25 |
26 | beforeEach(() => {
27 | cache.mockReset();
28 | fetchLib.mockReset();
29 | });
30 |
31 | describe('fetch', () => {
32 | it('should pull from cache if exists', (done) => {
33 | data.fetch({}, (err, res) => {
34 | if (err) {
35 | done(err);
36 | }
37 |
38 | expect(res).to.equal(cache.get());
39 | done();
40 | });
41 | });
42 |
43 | it('should fetch if not in cache', (done) => {
44 | data.fetch({ resource: 'miss' }, (err, res) => {
45 | if (err) {
46 | done(err);
47 | }
48 |
49 | expect(res).to.equal('fetch');
50 | done();
51 | });
52 | });
53 |
54 | it('should fetch using find spec if not in cache', (done) => {
55 | data.fetch({ resource: 'find' }, (err, res) => {
56 | if (err) {
57 | done(err);
58 | }
59 |
60 | const callCounts = cache.mockCounts();
61 | const params = fetchLib.mockParams();
62 |
63 | expect(callCounts.get).to.equal(1);
64 | expect(callCounts.find).to.equal(1);
65 | expect(params).to.equal(cache.find());
66 | expect(res).to.equal('fetch');
67 | done();
68 | });
69 | });
70 | });
71 |
72 | describe('initialize', () => {
73 | it('should initialize', (done) => {
74 | data.initialize(done);
75 | });
76 | });
77 |
78 | describe('update', () => {
79 | it('should update', (done) => {
80 | data.update(done);
81 | });
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/src/application/server/sitemap.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | *
5 | * Handle sitemap request.
6 | *
7 | * Reminder: There is a compression minimum threshold below which no compression
8 | * occurs.
9 | */
10 | import debugLib from 'debug';
11 | import urlLib from 'url';
12 | import sitemapLib from 'sitemap-xml';
13 | import utils from 'utils/node';
14 | import configs from 'configs';
15 | import serviceData from './services/data';
16 |
17 | const debug = debugLib('server:sitemap');
18 |
19 | const config = configs.create();
20 | const settings = config.settings;
21 |
22 | /**
23 | * Handle requests for sitemap.xml.
24 | *
25 | * @param {Object} req - The request object, not used.
26 | * @param {Object} res - The response object.
27 | * @param {Object} next - The next object.
28 | */
29 | export default function sitemap (req, res, next) {
30 | debug('Read routes');
31 |
32 | utils
33 | .nodeCall(serviceData.fetch, {
34 | resource: config.data.FRED.mainResource
35 | })
36 | .then((result) => {
37 | const routes = result.content,
38 | ssl = settings.web.ssl || settings.web.sslRemote,
39 | stream = sitemapLib();
40 |
41 | res.header('Content-Type', 'text/xml');
42 | stream.pipe(res);
43 |
44 | Object.keys(routes)
45 | .filter((key) => {
46 | return routes[key].mainNav;
47 | })
48 | .forEach((key) => {
49 | stream.write({
50 | loc: urlLib.format({
51 | protocol: ssl ? 'https' : 'http',
52 | hostname: settings.web.appHostname,
53 | pathname: routes[key].path
54 | }),
55 | priority: routes[key].siteMeta ?
56 | routes[key].siteMeta.priority : 1.0,
57 | changefreq: routes[key].siteMeta ?
58 | routes[key].siteMeta.changefreq : 'monthly'
59 | });
60 | });
61 |
62 | stream.end();
63 | })
64 | .catch((err) => {
65 | debug('Request failed: ', err);
66 | err.status = err.statusCode = (err.statusCode || err.status || 500);
67 | next(err);
68 | });
69 | }
70 |
--------------------------------------------------------------------------------
/src/application/actions/routes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | import debugLib from 'debug';
6 | import { createFluxibleRouteTransformer } from 'utils';
7 |
8 | const debug = debugLib('actions:routes');
9 |
10 | /**
11 | * The routes action.
12 | * This action is only executed on the server in this example to get
13 | * primary application routes into app state. However, this action could be
14 | * reused to retrieve or populate additional in-app routes later.
15 | * If routes are not passed in, it retrieves payload.resource from the 'routes' service.
16 | *
17 | * @param {Object} context - The fluxible action context.
18 | * @param {Object} payload - The action payload.
19 | * @param {Function} [payload.transform] - An optional custom route transformer.
20 | * @param {Object} [payload.routes] - Optional routes to add to the app without a service request.
21 | * @param {String} payload.resource - The name of the routes resource to retrieve with a service request.
22 | */
23 | export function routes (context, payload, done) {
24 | // This is done late in case routes (this) in interface, TODO: revisit.
25 | const actions = require('application/actions/interface').getActions();
26 |
27 | const transformer = (typeof payload.transform === 'function' ?
28 | payload.transform : createFluxibleRouteTransformer({
29 | actions
30 | }).jsonToFluxible);
31 |
32 | if (payload.routes) {
33 | let fluxibleRoutes = payload.routes;
34 |
35 | if (payload.transform) {
36 | debug('transforming routes');
37 |
38 | fluxibleRoutes = transformer(payload.routes);
39 | }
40 |
41 | context.dispatch('RECEIVE_ROUTES', fluxibleRoutes);
42 | return done();
43 | }
44 |
45 | debug('Routes request start');
46 | context.service.read('routes', payload, {}, function (err, routes) {
47 | debug('Routes request complete');
48 |
49 | if (err) {
50 | return done(err);
51 | }
52 |
53 | const fluxibleRoutes = transformer(routes);
54 | context.dispatch('RECEIVE_ROUTES', fluxibleRoutes);
55 | done(null, fluxibleRoutes);
56 | });
57 | }
58 |
59 | export default routes;
60 |
--------------------------------------------------------------------------------
/src/node_modules/configs/index.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | *
5 | * The server-side main config loader.
6 | *
7 | */
8 | 'use strict';
9 |
10 | var fs = require('fs');
11 | var path = require('path');
12 | var nconf = require('nconf');
13 |
14 | var localEnv = 'local.env.json';
15 |
16 | // Files to exclude when building the configuration objects.
17 | var exclude = [ 'index.js', localEnv ];
18 |
19 | /**
20 | * Loads modules in this directory.
21 | * Creates an object keyed by modules names found in this directory.
22 | *
23 | * @param {Object} nconf - The nconfig object.
24 | * @returns {Object} An object that contains all the configuration objects
25 | * keyed by module name. Configuration objects are formed by the
26 | * module export functions of modules found in this directory.
27 | */
28 | function configs (nconf) {
29 | var result = {};
30 | fs.readdirSync(__dirname).forEach(function (item) {
31 | var name = path.basename(item);
32 | if (exclude.indexOf(name) === -1) {
33 | result[name] = require('./' + name)(nconf);
34 | }
35 | });
36 | return result;
37 | }
38 |
39 | /**
40 | * Create a new configuration object, applying any overrides.
41 | *
42 | * Creates and tears off a new configuration object formed by the precedence:
43 | * 1. Overrides
44 | * 2. The process environment
45 | * 3. The local environment file
46 | * 4. Configuration object modules found in this directory
47 | *
48 | * @param {Object} overrides - highest priority configuration overrides.
49 | * @returns {Object} An object containing a copy of the full configuration.
50 | */
51 | function create (overrides) {
52 | nconf
53 | .overrides(overrides || {})
54 | .env()
55 | .file({ file: path.join(__dirname, localEnv) })
56 | .defaults(configs(nconf));
57 |
58 | var config = nconf.get();
59 |
60 | // Remove all the items that pass the filter
61 | Object.keys(config).filter(function (key) {
62 | return /^(?:npm)?_/.test(key);
63 | }).forEach(function (key) {
64 | delete config[key];
65 | });
66 |
67 | return config;
68 | }
69 |
70 | module.exports = {
71 | create: create
72 | };
73 |
--------------------------------------------------------------------------------
/src/build/nodemon.js:
--------------------------------------------------------------------------------
1 | /***
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | /* global Set */
6 | import path from 'path';
7 | import gulpNodemon from 'gulp-nodemon';
8 |
9 | /**
10 | * Factory for the nodemon task.
11 | *
12 | * @param {Object} settings - The project settings.
13 | * @param {String} target - One of ['dev', 'debug', 'perf', 'prod'].
14 | * @returns {Function} The nodemon task.
15 | */
16 | export default function nodemonTaskFactory (settings, target) {
17 | const legacyWatchOptions = {
18 | legacyWatch: true,
19 | pollingInterval: 250
20 | };
21 |
22 | const debugTarget = ['debug', 'inspect'].indexOf(target) > -1;
23 |
24 | const options = {
25 | ignore: [
26 | settings.src.serviceWorker.data,
27 | settings.src.serviceWorker.precache,
28 | settings.src.assetsJson,
29 | settings.src.assetsRevManifest
30 | ],
31 | ignoreRoot: [
32 | 'build',
33 | 'tests',
34 | 'node_modules/application'
35 | ],
36 | verbose: true,
37 | ext: 'js jsx scss',
38 | watch: settings.src.baseDir,
39 | tasks: (changedFiles) => {
40 | const buildTarget = debugTarget ? 'dev' : target;
41 | const tasks = new Set();
42 |
43 | changedFiles.forEach((file) => {
44 | if (/\.js.?$/.test(file)) {
45 | if (/\/sw\/?/.test(path.dirname(file))) {
46 | tasks.add(`bundlesSw_${buildTarget}`);
47 | } else {
48 | tasks.add(`bundlesMain_${buildTarget}`);
49 | }
50 | } else {
51 | // Must be scss
52 | tasks.add(`ccss_${buildTarget}`);
53 | }
54 | });
55 |
56 | return Array.from(tasks);
57 | }
58 | };
59 |
60 | // This is a workaround for an OSX/chokidar issue I'm experiencing (#217)
61 | if (process.platform === 'darwin') {
62 | Object.assign(options, legacyWatchOptions);
63 | }
64 |
65 | if (debugTarget) {
66 | options.nodeArgs = ['--debug-brk'];
67 | if (target === 'inspect') {
68 | options.nodeArgs.push('--inspect');
69 | }
70 | }
71 |
72 | return function nodemon (done) {
73 | gulpNodemon(options);
74 | done();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/tests/node_modules/test/mocks/sw-utils-idb-treo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | *
5 | * Mock treo for sw/utils/idb
6 | */
7 | 'use strict';
8 |
9 | var closeCount = 0;
10 | var mockValue;
11 | var mockReporter;
12 |
13 | function TreoStoreMock () {}
14 | ['all', 'batch', 'del', 'get', 'put'].forEach(function (method) {
15 | TreoStoreMock.prototype[method] = function () {
16 | var lastTwoArgs = Array.prototype.slice.call(arguments, -2),
17 | cb = lastTwoArgs[1] || lastTwoArgs[0],
18 | test = lastTwoArgs[0];
19 |
20 | // error emulation is a little specific/funky for this
21 | if (test === 'emulateError') {
22 | return cb(new Error('mock error'));
23 | }
24 |
25 | if (mockReporter) {
26 | mockReporter.apply(mockReporter,
27 | [method].concat(Array.prototype.slice.call(arguments)));
28 | }
29 |
30 | // allow purposed falsy flow, default is undefined for mock value.
31 | if (typeof mockValue === 'undefined') {
32 | return cb(null, 'mock value');
33 | }
34 |
35 | cb(null, mockValue);
36 | };
37 | });
38 |
39 | function TreoDBMock () {
40 | closeCount = 0;
41 | }
42 | TreoDBMock.prototype = {
43 | close: function () {
44 | closeCount++;
45 | },
46 | store: function () {
47 | return new TreoStoreMock();
48 | }
49 | };
50 |
51 | function TreoMock () {
52 | return new TreoDBMock();
53 | }
54 | TreoMock.schema = function treoMockSchema () {
55 | return {
56 | version: function () {
57 | return {
58 | addStore: function () {}
59 | };
60 | }
61 | };
62 | };
63 |
64 | /***
65 | * Mock only methods and properties
66 | */
67 | TreoMock.status = {
68 | getCloseCount: function () {
69 | return closeCount;
70 | }
71 | };
72 | TreoMock.setValue = function (value) {
73 | mockValue = value;
74 | };
75 | TreoMock.getValue = function () {
76 | return mockValue;
77 | };
78 | TreoMock.setReporter = function (reporter) {
79 | mockReporter = reporter;
80 | };
81 | // Make it possible to wrap/chain reporters
82 | TreoMock.getReporter = function () {
83 | return mockReporter;
84 | };
85 |
86 | module.exports = TreoMock;
87 |
--------------------------------------------------------------------------------
/src/tests/node_modules/test/fixtures/about-response.js:
--------------------------------------------------------------------------------
1 | /** This is a generated file **/
2 | /**
3 | GENERATION_TIME = Tue May 17 2016 23:33:02 GMT-0400 (EDT)
4 | NODE_ENV = development
5 | FRED_URL = https://api.github.com/repos/localnerve/fred/contents/resources.json?ref=development
6 | **/
7 | /*eslint quotes:0 */
8 | module.exports = JSON.parse(JSON.stringify(
9 | {"models":{"LocalBusiness":{"legalName":"LocalNerve, LLC","alternateName":"LocalNerve","url":"http://localnerve.com","telephone":"207-370-8005","email":"alex@localnerve.com","address":{"streetAddress":"PO BOX 95","addressRegion":"ME","addressLocality":"Windham","addressCountry":"USA","postalCode":"04062","postOfficeBoxNumber":"95"}},"SiteInfo":{"site":{"name":"Contactor","tagLine":"A Fluxible, Reactive reference app with a good prognosis.","bullets":["Fluxible","React","Data Driven"]},"license":{"type":"BSD","url":"https://github.com/localnerve/react-pwa-reference/blob/master/LICENSE.md","statement":"All code licensed under LocalNerve BSD License."},"developer":{"name":"LocalNerve","byLine":"Developed by LocalNerve","url":"http://localnerve.com"},"social":{"github":"https://github.com/localnerve/react-pwa-reference","twitter":"https://twitter.com/localnerve","facebook":"https://facebook.com/localnerve","linkedin":"https://www.linkedin.com/in/alexpaulgrant","googleplus":"https://plus.google.com/118303375063449115817/"}},"Settings":{"component":"settings","split":"settings","resource":"settings","url":"https://api.github.com/repos/localnerve/fred/contents/pages/settings.json","format":"json","action":{"name":"settings","params":{}},"models":["LocalBusiness","SiteInfo","Settings"]}},"content":"
About This Demo
\n
This demo is only meant to be a reference application. It serves as a possible source of inspiration for solving problems during isomorphic application development, or as a basis for a progressive application.
\n
The outgoing mail queue is currently disconnected from this application on Heroku, so contact form completions will not succeed.
\n
Please visit the project documentation for more detailed info, or contact me @localnerve.
\n"}
10 | ));
--------------------------------------------------------------------------------
/src/tests/unit/services/subscription.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
3 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
4 | */
5 | /* global describe, before, after, it */
6 | 'use strict';
7 |
8 | var expect = require('chai').expect;
9 | var mocks = require('test/mocks');
10 |
11 | describe('subscription service', function () {
12 | var subscription;
13 |
14 | before(function () {
15 | this.timeout(5000);
16 |
17 | mocks.serviceSubscription.begin();
18 | subscription = require('application/server/services/subscription');
19 | });
20 |
21 | after(function () {
22 | mocks.serviceSubscription.end();
23 | });
24 |
25 | function subCall (method, done) {
26 | var args = [];
27 | if (method === 'read' || method === 'delete') {
28 | args.push(
29 | null, null, {}, null
30 | );
31 | } else {
32 | args.push(
33 | null, null, {}, {}, null
34 | );
35 | }
36 | args.push(function (err) {
37 | if (err) {
38 | return done(err);
39 | }
40 | done();
41 | });
42 | subscription[method].apply(subscription, args);
43 | }
44 |
45 | describe('object', function () {
46 | it('should have name and create members', function () {
47 | expect(subscription.name).to.be.a('string');
48 | expect(subscription.create).to.be.a('function');
49 | expect(subscription.read).to.be.a('function');
50 | expect(subscription.update).to.be.a('function');
51 | expect(subscription.delete).to.be.a('function');
52 | });
53 | });
54 |
55 | describe('create', function () {
56 | it('should return a valid response', function (done) {
57 | subCall('create', done);
58 | });
59 | });
60 |
61 | describe('read', function () {
62 | it('should return a valid response', function (done) {
63 | subCall('read', done);
64 | });
65 | });
66 |
67 | describe('update', function () {
68 | it('should return a valid response', function (done) {
69 | subCall('update', done);
70 | });
71 | });
72 |
73 | describe('delete', function () {
74 | it('should return a valid response', function (done) {
75 | subCall('delete', done);
76 | });
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/src/tests/workers/contact/contact.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /**
3 | * Copyright (c) 2016 - 2022 Alex Grant (@localnerve), LocalNerve LLC
4 | * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
5 | *
6 | * Manual test harness for mail and queue service exercise.
7 | *
8 | * PREREQUISITES:
9 | * Must have environment setup for mail and queue settings, auth.
10 | * Must have the services implied by those settings setup and operational.
11 | * (Must have AMQP queue running and pointed to by contact.queue.url, etc.)
12 | *
13 | * Plunks a message onto the queue then starts the worker to consume it.
14 | * Kills the worker after some constant time elapsed.
15 | *
16 | * Must manually verify mail and queue status is as expected.
17 | */
18 | /*eslint-disable no-console */
19 | const spawn = require('child_process').spawn;
20 |
21 | require('@babel/register')({
22 | presets: [
23 | '@babel/env'
24 | ],
25 | ignore: []
26 | });
27 |
28 | const mail = require('application/server/services/mail');
29 | const contact = require('configs').create().contact;
30 |
31 | const workerProcess = 'application/server/workers/contact/bin/contact';
32 | const workTime = 10000;
33 |
34 | const prereqs = contact.mail.username() && contact.mail.password() &&
35 | contact.mail.service();
36 |
37 | if (!prereqs) {
38 | console.error(
39 | 'mail service environmental prerequisites missing. Check environment.'
40 | );
41 | console.error('mail config');
42 | console.error(`service = ${contact.mail.service()}`);
43 | console.error(`to = ${contact.mail.to()}`);
44 | console.error(`from = ${contact.mail.from()}`);
45 | console.error(`username = ${contact.mail.username()}`);
46 | console.error(`password = ${contact.mail.password()}`);
47 | process.exit();
48 | }
49 |
50 | mail.send({
51 | name: 'Manual Test',
52 | email: 'manual@test.local',
53 | message: 'This is a test message from the manual test harness.'
54 | }, function (err) {
55 | if (err) {
56 | throw err;
57 | }
58 |
59 | const cp = spawn(require.resolve(workerProcess));
60 |
61 | cp.on('close', function () {
62 | console.log(`${workerProcess} complete`);
63 | process.exit();
64 | });
65 |
66 | setTimeout(function () {
67 | cp.kill('SIGINT');
68 | }, workTime);
69 | });
70 |
--------------------------------------------------------------------------------
/src/tests/node_modules/test/fixtures/routes-response.js:
--------------------------------------------------------------------------------
1 | /** This is a generated file **/
2 | /**
3 | GENERATION_TIME = Tue May 17 2016 23:33:02 GMT-0400 (EDT)
4 | NODE_ENV = development
5 | FRED_URL = https://api.github.com/repos/localnerve/fred/contents/resources.json?ref=development
6 | **/
7 | /*eslint quotes:0 */
8 | module.exports = JSON.parse(JSON.stringify(
9 | {"404":{"path":"/404","method":"get","page":"404","label":"Not Found","pageTitle":"Page Not Found","component":"ContentPage","mainNav":false,"background":"","order":0,"priority":0,"action":{"name":"page","params":{"resource":"404","url":"https://api.github.com/repos/localnerve/fred/contents/pages/404.md","format":"markdown","models":["LocalBusiness","SiteInfo","Settings"]}}},"500":{"path":"/500","method":"get","page":"500","label":"Error","pageTitle":"Application Error","component":"ContentPage","mainNav":false,"background":"","order":0,"priority":0,"action":{"name":"page","params":{"resource":"500","url":"https://api.github.com/repos/localnerve/fred/contents/pages/500.md","format":"markdown","models":["LocalBusiness","SiteInfo","Settings"]}}},"home":{"path":"/","method":"get","page":"home","label":"Home","pageTitle":"An Example Isomorphic Application","component":"ContentPage","mainNav":true,"background":"3.jpg","order":0,"priority":1,"siteMeta":{"priority":1,"changefreq":"weekly"},"action":{"name":"page","params":{"resource":"home","url":"https://api.github.com/repos/localnerve/fred/contents/pages/home.md","format":"markdown","models":["LocalBusiness","SiteInfo","Settings"]}}},"about":{"path":"/about","method":"get","page":"about","label":"About","pageTitle":"About","component":"ContentPage","mainNav":true,"background":"4.jpg","order":1,"priority":1,"siteMeta":{"priority":0.5,"changefreq":"monthly"},"action":{"name":"page","params":{"resource":"about","url":"https://api.github.com/repos/localnerve/fred/contents/pages/about.md","format":"markdown","models":["LocalBusiness","SiteInfo","Settings"]}}},"contact":{"path":"/contact","method":"get","page":"contact","label":"Contact","pageTitle":"Contact","component":"Contact","mainNav":true,"background":"5.jpg","order":2,"priority":1,"siteMeta":{"priority":0.8,"changefreq":"monthly"},"action":{"name":"page","params":{"resource":"contact","url":"https://api.github.com/repos/localnerve/fred/contents/pages/contact.json","format":"json","models":["LocalBusiness","SiteInfo","Settings"]}}}}
10 | ));
--------------------------------------------------------------------------------