├── .buildkite └── pipeline.yml ├── .eslintignore ├── .eslintrc.js ├── .flowconfig ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── bin └── cli.js ├── docker-compose.yml ├── flow-typed └── npm │ └── jest_v22.x.x.js ├── package.json ├── renovate.json ├── templates └── basic │ ├── content │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── flow-typed │ │ └── globals.js │ ├── package.json │ ├── src │ │ ├── main.js │ │ ├── pages │ │ │ ├── home.js │ │ │ └── pageNotFound.js │ │ └── root.js │ └── yarn.lock │ └── index.js ├── test-utils └── test-utils.js ├── tests └── test.js └── yarn.lock /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: ':docker: :package:' 3 | plugins: 4 | 'docker-compose#v1.7.0': 5 | build: create-fusion-app 6 | image-repository: 027047743804.dkr.ecr.us-east-2.amazonaws.com/uber 7 | agents: 8 | queue: builders 9 | - name: ':docker: :package: node8' 10 | plugins: 11 | 'docker-compose#v1.7.0': 12 | build: create-fusion-app-node-last 13 | image-repository: 027047743804.dkr.ecr.us-east-2.amazonaws.com/uber 14 | agents: 15 | queue: builders 16 | - wait 17 | - command: yarn flow check 18 | name: ':flowtype:' 19 | plugins: 20 | 'docker-compose#v1.7.0': 21 | run: create-fusion-app 22 | agents: 23 | queue: workers 24 | - command: yarn flow check 25 | name: ':flowtype: node8' 26 | plugins: 27 | 'docker-compose#v1.7.0': 28 | run: create-fusion-app-node-last 29 | agents: 30 | queue: workers 31 | - name: ':eslint:' 32 | command: yarn lint 33 | plugins: 34 | 'docker-compose#v1.7.0': 35 | run: create-fusion-app 36 | agents: 37 | queue: workers 38 | - name: ':eslint: node8' 39 | command: yarn lint 40 | plugins: 41 | 'docker-compose#v1.7.0': 42 | run: create-fusion-app-node-last 43 | agents: 44 | queue: workers 45 | - name: ':jest:' 46 | command: yarn jest 47 | plugins: 48 | 'docker-compose#v1.7.0': 49 | run: create-fusion-app 50 | agents: 51 | queue: workers 52 | - name: ':jest: node8' 53 | command: yarn jest 54 | plugins: 55 | 'docker-compose#v1.7.0': 56 | run: create-fusion-app-node-last 57 | agents: 58 | queue: workers 59 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | flow-typed/* 2 | node_modules/ 3 | templates/ 4 | test-artifacts/ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | require.resolve('eslint-config-fusion') 4 | ] 5 | }; 6 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/.*[^(package)]\.json$ 3 | .*/templates/.* 4 | .*/test-artifacts/.* 5 | 6 | [include] 7 | ./bin/ 8 | ./templates/ 9 | ./tests/ 10 | 11 | [libs] 12 | ./templates/basic/content/flow-typed/globals.js 13 | 14 | [lints] 15 | 16 | [options] 17 | 18 | [strict] 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn-error.log 3 | test-artifacts/ 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=uber/web-base-image:2.0.0 2 | FROM $BASE_IMAGE 3 | 4 | WORKDIR /create-fusion-app 5 | 6 | COPY package.json yarn.lock /create-fusion-app/ 7 | 8 | RUN yarn 9 | 10 | COPY templates/basic/content/package.json templates/basic/content/yarn.lock /create-fusion-app/templates/basic/content/ 11 | 12 | RUN cd templates/basic/content && yarn --ignore-scripts 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Uber Technologies, Inc. 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 | # Create Fusion.js App 2 | 3 | [![Build status](https://badge.buildkite.com/bb6f46e9ba824ba83b2c79ddd483d954ab36a98d022220611f.svg?branch=master)](https://buildkite.com/uberopensource/create-fusion-app) 4 | 5 | Creates a Fusion.js application using the command line. 6 | 7 | ## Usage 8 | 9 | ``` 10 | yarn create fusion-app 11 | ``` 12 | 13 | ## Documentation 14 | 15 | Read Fusion.js documentation and learn how to get help at: https://fusionjs.com 16 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // @flow 3 | /* eslint-env node */ 4 | /* eslint-disable no-console */ 5 | 6 | const path = require('path'); 7 | const chalk = require('chalk'); 8 | const scaffold = require('fusion-scaffolder'); 9 | 10 | const projectName = process.argv[2]; 11 | 12 | if (!projectName) { 13 | console.log(`${chalk.red('Could not create application.')} 14 | 15 | Please specify the project directory: 16 | ${chalk.cyan('yarn create fusion-app')} ${chalk.green('')} 17 | 18 | Example: 19 | ${chalk.cyan('yarn create fusion-app')} ${chalk.green('my-fusionjs-app')} 20 | `); 21 | process.exit(1); 22 | } 23 | 24 | console.log(` 25 | Creating a new Fusion.js app in: ${chalk.green( 26 | `${process.cwd()}${path.sep}${projectName}` 27 | )} 28 | `); 29 | 30 | scaffold({ 31 | path: './templates/basic', 32 | cwd: path.join(__dirname, '..'), 33 | projectPath: path.join(process.cwd(), projectName), 34 | project: projectName, 35 | }) 36 | .then(() => { 37 | console.log(` 38 | ${chalk.green.bold(`Success! You have created a Fusion.js project.`)} 39 | 40 | Start your Fusion.js app with: 41 | ${chalk.cyan(`cd ${projectName} && yarn dev`)} 42 | `); 43 | }) 44 | .catch(e => { 45 | console.log('Error starting your application', e); 46 | }); 47 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | create-fusion-app: 4 | build: . 5 | volumes: 6 | - '.:/create-fusion-app' 7 | - /create-fusion-app/node_modules/ 8 | - /create-fusion-app/templates/basic/content/node_modules/ 9 | environment: 10 | - CODECOV_TOKEN 11 | - CI=true 12 | - BUILDKITE 13 | - BUILDKITE_BRANCH 14 | - BUILDKITE_BUILD_NUMBER 15 | - BUILDKITE_JOB_ID 16 | - BUILDKITE_BUILD_URL 17 | - BUILDKITE_PROJECT_SLUG 18 | - BUILDKITE_COMMIT 19 | create-fusion-app-node-last: 20 | extends: create-fusion-app 21 | build: 22 | context: . 23 | args: 24 | BASE_IMAGE: 'uber/web-base-image:1.0.9' 25 | -------------------------------------------------------------------------------- /flow-typed/npm/jest_v22.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 5f6b80ba0fa4571aac1e7ea6e5fea425 2 | // flow-typed version: f4a7859cd3/jest_v22.x.x/flow_>=v0.39.x 3 | 4 | type JestMockFn, TReturn> = { 5 | (...args: TArguments): TReturn, 6 | /** 7 | * An object for introspecting mock calls 8 | */ 9 | mock: { 10 | /** 11 | * An array that represents all calls that have been made into this mock 12 | * function. Each call is represented by an array of arguments that were 13 | * passed during the call. 14 | */ 15 | calls: Array, 16 | /** 17 | * An array that contains all the object instances that have been 18 | * instantiated from this mock function. 19 | */ 20 | instances: Array 21 | }, 22 | /** 23 | * Resets all information stored in the mockFn.mock.calls and 24 | * mockFn.mock.instances arrays. Often this is useful when you want to clean 25 | * up a mock's usage data between two assertions. 26 | */ 27 | mockClear(): void, 28 | /** 29 | * Resets all information stored in the mock. This is useful when you want to 30 | * completely restore a mock back to its initial state. 31 | */ 32 | mockReset(): void, 33 | /** 34 | * Removes the mock and restores the initial implementation. This is useful 35 | * when you want to mock functions in certain test cases and restore the 36 | * original implementation in others. Beware that mockFn.mockRestore only 37 | * works when mock was created with jest.spyOn. Thus you have to take care of 38 | * restoration yourself when manually assigning jest.fn(). 39 | */ 40 | mockRestore(): void, 41 | /** 42 | * Accepts a function that should be used as the implementation of the mock. 43 | * The mock itself will still record all calls that go into and instances 44 | * that come from itself -- the only difference is that the implementation 45 | * will also be executed when the mock is called. 46 | */ 47 | mockImplementation( 48 | fn: (...args: TArguments) => TReturn 49 | ): JestMockFn, 50 | /** 51 | * Accepts a function that will be used as an implementation of the mock for 52 | * one call to the mocked function. Can be chained so that multiple function 53 | * calls produce different results. 54 | */ 55 | mockImplementationOnce( 56 | fn: (...args: TArguments) => TReturn 57 | ): JestMockFn, 58 | /** 59 | * Accepts a string to use in test result output in place of "jest.fn()" to 60 | * indicate which mock function is being referenced. 61 | */ 62 | mockName(name: string): JestMockFn, 63 | /** 64 | * Just a simple sugar function for returning `this` 65 | */ 66 | mockReturnThis(): void, 67 | /** 68 | * Deprecated: use jest.fn(() => value) instead 69 | */ 70 | mockReturnValue(value: TReturn): JestMockFn, 71 | /** 72 | * Sugar for only returning a value once inside your mock 73 | */ 74 | mockReturnValueOnce(value: TReturn): JestMockFn 75 | }; 76 | 77 | type JestAsymmetricEqualityType = { 78 | /** 79 | * A custom Jasmine equality tester 80 | */ 81 | asymmetricMatch(value: mixed): boolean 82 | }; 83 | 84 | type JestCallsType = { 85 | allArgs(): mixed, 86 | all(): mixed, 87 | any(): boolean, 88 | count(): number, 89 | first(): mixed, 90 | mostRecent(): mixed, 91 | reset(): void 92 | }; 93 | 94 | type JestClockType = { 95 | install(): void, 96 | mockDate(date: Date): void, 97 | tick(milliseconds?: number): void, 98 | uninstall(): void 99 | }; 100 | 101 | type JestMatcherResult = { 102 | message?: string | (() => string), 103 | pass: boolean 104 | }; 105 | 106 | type JestMatcher = (actual: any, expected: any) => JestMatcherResult; 107 | 108 | type JestPromiseType = { 109 | /** 110 | * Use rejects to unwrap the reason of a rejected promise so any other 111 | * matcher can be chained. If the promise is fulfilled the assertion fails. 112 | */ 113 | rejects: JestExpectType, 114 | /** 115 | * Use resolves to unwrap the value of a fulfilled promise so any other 116 | * matcher can be chained. If the promise is rejected the assertion fails. 117 | */ 118 | resolves: JestExpectType 119 | }; 120 | 121 | /** 122 | * Jest allows functions and classes to be used as test names in test() and 123 | * describe() 124 | */ 125 | type JestTestName = string | Function; 126 | 127 | /** 128 | * Plugin: jest-enzyme 129 | */ 130 | type EnzymeMatchersType = { 131 | toBeChecked(): void, 132 | toBeDisabled(): void, 133 | toBeEmpty(): void, 134 | toBeEmptyRender(): void, 135 | toBePresent(): void, 136 | toContainReact(element: React$Element): void, 137 | toExist(): void, 138 | toHaveClassName(className: string): void, 139 | toHaveHTML(html: string): void, 140 | toHaveProp: ((propKey: string, propValue?: any) => void) & ((props: Object) => void), 141 | toHaveRef(refName: string): void, 142 | toHaveState: ((stateKey: string, stateValue?: any) => void) & ((state: Object) => void), 143 | toHaveStyle: ((styleKey: string, styleValue?: any) => void) & ((style: Object) => void), 144 | toHaveTagName(tagName: string): void, 145 | toHaveText(text: string): void, 146 | toIncludeText(text: string): void, 147 | toHaveValue(value: any): void, 148 | toMatchElement(element: React$Element): void, 149 | toMatchSelector(selector: string): void 150 | }; 151 | 152 | // DOM testing library extensions https://github.com/kentcdodds/dom-testing-library#custom-jest-matchers 153 | type DomTestingLibraryType = { 154 | toBeInTheDOM(): void, 155 | toHaveTextContent(content: string): void, 156 | toHaveAttribute(name: string, expectedValue?: string): void 157 | }; 158 | 159 | // Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers 160 | type JestJQueryMatchersType = { 161 | toExist(): void, 162 | toHaveLength(len: number): void, 163 | toHaveId(id: string): void, 164 | toHaveClass(className: string): void, 165 | toHaveTag(tag: string): void, 166 | toHaveAttr(key: string, val?: any): void, 167 | toHaveProp(key: string, val?: any): void, 168 | toHaveText(text: string | RegExp): void, 169 | toHaveData(key: string, val?: any): void, 170 | toHaveValue(val: any): void, 171 | toHaveCss(css: {[key: string]: any}): void, 172 | toBeChecked(): void, 173 | toBeDisabled(): void, 174 | toBeEmpty(): void, 175 | toBeHidden(): void, 176 | toBeSelected(): void, 177 | toBeVisible(): void, 178 | toBeFocused(): void, 179 | toBeInDom(): void, 180 | toBeMatchedBy(sel: string): void, 181 | toHaveDescendant(sel: string): void, 182 | toHaveDescendantWithText(sel: string, text: string | RegExp): void 183 | }; 184 | 185 | 186 | // Jest Extended Matchers: https://github.com/jest-community/jest-extended 187 | type JestExtendedMatchersType = { 188 | /** 189 | * Note: Currently unimplemented 190 | * Passing assertion 191 | * 192 | * @param {String} message 193 | */ 194 | // pass(message: string): void; 195 | 196 | /** 197 | * Note: Currently unimplemented 198 | * Failing assertion 199 | * 200 | * @param {String} message 201 | */ 202 | // fail(message: string): void; 203 | 204 | /** 205 | * Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty. 206 | */ 207 | toBeEmpty(): void; 208 | 209 | /** 210 | * Use .toBeOneOf when checking if a value is a member of a given Array. 211 | * @param {Array.<*>} members 212 | */ 213 | toBeOneOf(members: any[]): void; 214 | 215 | /** 216 | * Use `.toBeNil` when checking a value is `null` or `undefined`. 217 | */ 218 | toBeNil(): void; 219 | 220 | /** 221 | * Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`. 222 | * @param {Function} predicate 223 | */ 224 | toSatisfy(predicate: (n: any) => boolean): void; 225 | 226 | /** 227 | * Use `.toBeArray` when checking if a value is an `Array`. 228 | */ 229 | toBeArray(): void; 230 | 231 | /** 232 | * Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x. 233 | * @param {Number} x 234 | */ 235 | toBeArrayOfSize(x: number): void; 236 | 237 | /** 238 | * Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set. 239 | * @param {Array.<*>} members 240 | */ 241 | toIncludeAllMembers(members: any[]): void; 242 | 243 | /** 244 | * Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set. 245 | * @param {Array.<*>} members 246 | */ 247 | toIncludeAnyMembers(members: any[]): void; 248 | 249 | /** 250 | * Use `.toSatisfyAll` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean` for all values in an array. 251 | * @param {Function} predicate 252 | */ 253 | toSatisfyAll(predicate: (n: any) => boolean): void; 254 | 255 | /** 256 | * Use `.toBeBoolean` when checking if a value is a `Boolean`. 257 | */ 258 | toBeBoolean(): void; 259 | 260 | /** 261 | * Use `.toBeTrue` when checking a value is equal (===) to `true`. 262 | */ 263 | toBeTrue(): void; 264 | 265 | /** 266 | * Use `.toBeFalse` when checking a value is equal (===) to `false`. 267 | */ 268 | toBeFalse(): void; 269 | 270 | /** 271 | * Use .toBeDate when checking if a value is a Date. 272 | */ 273 | toBeDate(): void; 274 | 275 | /** 276 | * Use `.toBeFunction` when checking if a value is a `Function`. 277 | */ 278 | toBeFunction(): void; 279 | 280 | /** 281 | * Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`. 282 | * 283 | * Note: Required Jest version >22 284 | * Note: Your mock functions will have to be asynchronous to cause the timestamps inside of Jest to occur in a differentJS event loop, otherwise the mock timestamps will all be the same 285 | * 286 | * @param {Mock} mock 287 | */ 288 | toHaveBeenCalledBefore(mock: JestMockFn): void; 289 | 290 | /** 291 | * Use `.toBeNumber` when checking if a value is a `Number`. 292 | */ 293 | toBeNumber(): void; 294 | 295 | /** 296 | * Use `.toBeNaN` when checking a value is `NaN`. 297 | */ 298 | toBeNaN(): void; 299 | 300 | /** 301 | * Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`. 302 | */ 303 | toBeFinite(): void; 304 | 305 | /** 306 | * Use `.toBePositive` when checking if a value is a positive `Number`. 307 | */ 308 | toBePositive(): void; 309 | 310 | /** 311 | * Use `.toBeNegative` when checking if a value is a negative `Number`. 312 | */ 313 | toBeNegative(): void; 314 | 315 | /** 316 | * Use `.toBeEven` when checking if a value is an even `Number`. 317 | */ 318 | toBeEven(): void; 319 | 320 | /** 321 | * Use `.toBeOdd` when checking if a value is an odd `Number`. 322 | */ 323 | toBeOdd(): void; 324 | 325 | /** 326 | * Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive). 327 | * 328 | * @param {Number} start 329 | * @param {Number} end 330 | */ 331 | toBeWithin(start: number, end: number): void; 332 | 333 | /** 334 | * Use `.toBeObject` when checking if a value is an `Object`. 335 | */ 336 | toBeObject(): void; 337 | 338 | /** 339 | * Use `.toContainKey` when checking if an object contains the provided key. 340 | * 341 | * @param {String} key 342 | */ 343 | toContainKey(key: string): void; 344 | 345 | /** 346 | * Use `.toContainKeys` when checking if an object has all of the provided keys. 347 | * 348 | * @param {Array.} keys 349 | */ 350 | toContainKeys(keys: string[]): void; 351 | 352 | /** 353 | * Use `.toContainAllKeys` when checking if an object only contains all of the provided keys. 354 | * 355 | * @param {Array.} keys 356 | */ 357 | toContainAllKeys(keys: string[]): void; 358 | 359 | /** 360 | * Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys. 361 | * 362 | * @param {Array.} keys 363 | */ 364 | toContainAnyKeys(keys: string[]): void; 365 | 366 | /** 367 | * Use `.toContainValue` when checking if an object contains the provided value. 368 | * 369 | * @param {*} value 370 | */ 371 | toContainValue(value: any): void; 372 | 373 | /** 374 | * Use `.toContainValues` when checking if an object contains all of the provided values. 375 | * 376 | * @param {Array.<*>} values 377 | */ 378 | toContainValues(values: any[]): void; 379 | 380 | /** 381 | * Use `.toContainAllValues` when checking if an object only contains all of the provided values. 382 | * 383 | * @param {Array.<*>} values 384 | */ 385 | toContainAllValues(values: any[]): void; 386 | 387 | /** 388 | * Use `.toContainAnyValues` when checking if an object contains at least one of the provided values. 389 | * 390 | * @param {Array.<*>} values 391 | */ 392 | toContainAnyValues(values: any[]): void; 393 | 394 | /** 395 | * Use `.toContainEntry` when checking if an object contains the provided entry. 396 | * 397 | * @param {Array.} entry 398 | */ 399 | toContainEntry(entry: [string, string]): void; 400 | 401 | /** 402 | * Use `.toContainEntries` when checking if an object contains all of the provided entries. 403 | * 404 | * @param {Array.>} entries 405 | */ 406 | toContainEntries(entries: [string, string][]): void; 407 | 408 | /** 409 | * Use `.toContainAllEntries` when checking if an object only contains all of the provided entries. 410 | * 411 | * @param {Array.>} entries 412 | */ 413 | toContainAllEntries(entries: [string, string][]): void; 414 | 415 | /** 416 | * Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries. 417 | * 418 | * @param {Array.>} entries 419 | */ 420 | toContainAnyEntries(entries: [string, string][]): void; 421 | 422 | /** 423 | * Use `.toBeExtensible` when checking if an object is extensible. 424 | */ 425 | toBeExtensible(): void; 426 | 427 | /** 428 | * Use `.toBeFrozen` when checking if an object is frozen. 429 | */ 430 | toBeFrozen(): void; 431 | 432 | /** 433 | * Use `.toBeSealed` when checking if an object is sealed. 434 | */ 435 | toBeSealed(): void; 436 | 437 | /** 438 | * Use `.toBeString` when checking if a value is a `String`. 439 | */ 440 | toBeString(): void; 441 | 442 | /** 443 | * Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings. 444 | * 445 | * @param {String} string 446 | */ 447 | toEqualCaseInsensitive(string: string): void; 448 | 449 | /** 450 | * Use `.toStartWith` when checking if a `String` starts with a given `String` prefix. 451 | * 452 | * @param {String} prefix 453 | */ 454 | toStartWith(prefix: string): void; 455 | 456 | /** 457 | * Use `.toEndWith` when checking if a `String` ends with a given `String` suffix. 458 | * 459 | * @param {String} suffix 460 | */ 461 | toEndWith(suffix: string): void; 462 | 463 | /** 464 | * Use `.toInclude` when checking if a `String` includes the given `String` substring. 465 | * 466 | * @param {String} substring 467 | */ 468 | toInclude(substring: string): void; 469 | 470 | /** 471 | * Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times. 472 | * 473 | * @param {String} substring 474 | * @param {Number} times 475 | */ 476 | toIncludeRepeated(substring: string, times: number): void; 477 | 478 | /** 479 | * Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings. 480 | * 481 | * @param {Array.} substring 482 | */ 483 | toIncludeMultiple(substring: string[]): void; 484 | }; 485 | 486 | type JestExpectType = { 487 | not: JestExpectType & EnzymeMatchersType & DomTestingLibraryType & JestJQueryMatchersType & JestExtendedMatchersType, 488 | /** 489 | * If you have a mock function, you can use .lastCalledWith to test what 490 | * arguments it was last called with. 491 | */ 492 | lastCalledWith(...args: Array): void, 493 | /** 494 | * toBe just checks that a value is what you expect. It uses === to check 495 | * strict equality. 496 | */ 497 | toBe(value: any): void, 498 | /** 499 | * Use .toHaveBeenCalled to ensure that a mock function got called. 500 | */ 501 | toBeCalled(): void, 502 | /** 503 | * Use .toBeCalledWith to ensure that a mock function was called with 504 | * specific arguments. 505 | */ 506 | toBeCalledWith(...args: Array): void, 507 | /** 508 | * Using exact equality with floating point numbers is a bad idea. Rounding 509 | * means that intuitive things fail. 510 | */ 511 | toBeCloseTo(num: number, delta: any): void, 512 | /** 513 | * Use .toBeDefined to check that a variable is not undefined. 514 | */ 515 | toBeDefined(): void, 516 | /** 517 | * Use .toBeFalsy when you don't care what a value is, you just want to 518 | * ensure a value is false in a boolean context. 519 | */ 520 | toBeFalsy(): void, 521 | /** 522 | * To compare floating point numbers, you can use toBeGreaterThan. 523 | */ 524 | toBeGreaterThan(number: number): void, 525 | /** 526 | * To compare floating point numbers, you can use toBeGreaterThanOrEqual. 527 | */ 528 | toBeGreaterThanOrEqual(number: number): void, 529 | /** 530 | * To compare floating point numbers, you can use toBeLessThan. 531 | */ 532 | toBeLessThan(number: number): void, 533 | /** 534 | * To compare floating point numbers, you can use toBeLessThanOrEqual. 535 | */ 536 | toBeLessThanOrEqual(number: number): void, 537 | /** 538 | * Use .toBeInstanceOf(Class) to check that an object is an instance of a 539 | * class. 540 | */ 541 | toBeInstanceOf(cls: Class<*>): void, 542 | /** 543 | * .toBeNull() is the same as .toBe(null) but the error messages are a bit 544 | * nicer. 545 | */ 546 | toBeNull(): void, 547 | /** 548 | * Use .toBeTruthy when you don't care what a value is, you just want to 549 | * ensure a value is true in a boolean context. 550 | */ 551 | toBeTruthy(): void, 552 | /** 553 | * Use .toBeUndefined to check that a variable is undefined. 554 | */ 555 | toBeUndefined(): void, 556 | /** 557 | * Use .toContain when you want to check that an item is in a list. For 558 | * testing the items in the list, this uses ===, a strict equality check. 559 | */ 560 | toContain(item: any): void, 561 | /** 562 | * Use .toContainEqual when you want to check that an item is in a list. For 563 | * testing the items in the list, this matcher recursively checks the 564 | * equality of all fields, rather than checking for object identity. 565 | */ 566 | toContainEqual(item: any): void, 567 | /** 568 | * Use .toEqual when you want to check that two objects have the same value. 569 | * This matcher recursively checks the equality of all fields, rather than 570 | * checking for object identity. 571 | */ 572 | toEqual(value: any): void, 573 | /** 574 | * Use .toHaveBeenCalled to ensure that a mock function got called. 575 | */ 576 | toHaveBeenCalled(): void, 577 | /** 578 | * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact 579 | * number of times. 580 | */ 581 | toHaveBeenCalledTimes(number: number): void, 582 | /** 583 | * Use .toHaveBeenCalledWith to ensure that a mock function was called with 584 | * specific arguments. 585 | */ 586 | toHaveBeenCalledWith(...args: Array): void, 587 | /** 588 | * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called 589 | * with specific arguments. 590 | */ 591 | toHaveBeenLastCalledWith(...args: Array): void, 592 | /** 593 | * Check that an object has a .length property and it is set to a certain 594 | * numeric value. 595 | */ 596 | toHaveLength(number: number): void, 597 | /** 598 | * 599 | */ 600 | toHaveProperty(propPath: string, value?: any): void, 601 | /** 602 | * Use .toMatch to check that a string matches a regular expression or string. 603 | */ 604 | toMatch(regexpOrString: RegExp | string): void, 605 | /** 606 | * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. 607 | */ 608 | toMatchObject(object: Object | Array): void, 609 | /** 610 | * This ensures that a React component matches the most recent snapshot. 611 | */ 612 | toMatchSnapshot(name?: string): void, 613 | /** 614 | * Use .toThrow to test that a function throws when it is called. 615 | * If you want to test that a specific error gets thrown, you can provide an 616 | * argument to toThrow. The argument can be a string for the error message, 617 | * a class for the error, or a regex that should match the error. 618 | * 619 | * Alias: .toThrowError 620 | */ 621 | toThrow(message?: string | Error | Class | RegExp): void, 622 | toThrowError(message?: string | Error | Class | RegExp): void, 623 | /** 624 | * Use .toThrowErrorMatchingSnapshot to test that a function throws a error 625 | * matching the most recent snapshot when it is called. 626 | */ 627 | toThrowErrorMatchingSnapshot(): void 628 | }; 629 | 630 | type JestObjectType = { 631 | /** 632 | * Disables automatic mocking in the module loader. 633 | * 634 | * After this method is called, all `require()`s will return the real 635 | * versions of each module (rather than a mocked version). 636 | */ 637 | disableAutomock(): JestObjectType, 638 | /** 639 | * An un-hoisted version of disableAutomock 640 | */ 641 | autoMockOff(): JestObjectType, 642 | /** 643 | * Enables automatic mocking in the module loader. 644 | */ 645 | enableAutomock(): JestObjectType, 646 | /** 647 | * An un-hoisted version of enableAutomock 648 | */ 649 | autoMockOn(): JestObjectType, 650 | /** 651 | * Clears the mock.calls and mock.instances properties of all mocks. 652 | * Equivalent to calling .mockClear() on every mocked function. 653 | */ 654 | clearAllMocks(): JestObjectType, 655 | /** 656 | * Resets the state of all mocks. Equivalent to calling .mockReset() on every 657 | * mocked function. 658 | */ 659 | resetAllMocks(): JestObjectType, 660 | /** 661 | * Restores all mocks back to their original value. 662 | */ 663 | restoreAllMocks(): JestObjectType, 664 | /** 665 | * Removes any pending timers from the timer system. 666 | */ 667 | clearAllTimers(): void, 668 | /** 669 | * The same as `mock` but not moved to the top of the expectation by 670 | * babel-jest. 671 | */ 672 | doMock(moduleName: string, moduleFactory?: any): JestObjectType, 673 | /** 674 | * The same as `unmock` but not moved to the top of the expectation by 675 | * babel-jest. 676 | */ 677 | dontMock(moduleName: string): JestObjectType, 678 | /** 679 | * Returns a new, unused mock function. Optionally takes a mock 680 | * implementation. 681 | */ 682 | fn, TReturn>( 683 | implementation?: (...args: TArguments) => TReturn 684 | ): JestMockFn, 685 | /** 686 | * Determines if the given function is a mocked function. 687 | */ 688 | isMockFunction(fn: Function): boolean, 689 | /** 690 | * Given the name of a module, use the automatic mocking system to generate a 691 | * mocked version of the module for you. 692 | */ 693 | genMockFromModule(moduleName: string): any, 694 | /** 695 | * Mocks a module with an auto-mocked version when it is being required. 696 | * 697 | * The second argument can be used to specify an explicit module factory that 698 | * is being run instead of using Jest's automocking feature. 699 | * 700 | * The third argument can be used to create virtual mocks -- mocks of modules 701 | * that don't exist anywhere in the system. 702 | */ 703 | mock( 704 | moduleName: string, 705 | moduleFactory?: any, 706 | options?: Object 707 | ): JestObjectType, 708 | /** 709 | * Returns the actual module instead of a mock, bypassing all checks on 710 | * whether the module should receive a mock implementation or not. 711 | */ 712 | requireActual(moduleName: string): any, 713 | /** 714 | * Returns a mock module instead of the actual module, bypassing all checks 715 | * on whether the module should be required normally or not. 716 | */ 717 | requireMock(moduleName: string): any, 718 | /** 719 | * Resets the module registry - the cache of all required modules. This is 720 | * useful to isolate modules where local state might conflict between tests. 721 | */ 722 | resetModules(): JestObjectType, 723 | /** 724 | * Exhausts the micro-task queue (usually interfaced in node via 725 | * process.nextTick). 726 | */ 727 | runAllTicks(): void, 728 | /** 729 | * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), 730 | * setInterval(), and setImmediate()). 731 | */ 732 | runAllTimers(): void, 733 | /** 734 | * Exhausts all tasks queued by setImmediate(). 735 | */ 736 | runAllImmediates(): void, 737 | /** 738 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 739 | * or setInterval() and setImmediate()). 740 | */ 741 | advanceTimersByTime(msToRun: number): void, 742 | /** 743 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 744 | * or setInterval() and setImmediate()). 745 | * 746 | * Renamed to `advanceTimersByTime`. 747 | */ 748 | runTimersToTime(msToRun: number): void, 749 | /** 750 | * Executes only the macro-tasks that are currently pending (i.e., only the 751 | * tasks that have been queued by setTimeout() or setInterval() up to this 752 | * point) 753 | */ 754 | runOnlyPendingTimers(): void, 755 | /** 756 | * Explicitly supplies the mock object that the module system should return 757 | * for the specified module. Note: It is recommended to use jest.mock() 758 | * instead. 759 | */ 760 | setMock(moduleName: string, moduleExports: any): JestObjectType, 761 | /** 762 | * Indicates that the module system should never return a mocked version of 763 | * the specified module from require() (e.g. that it should always return the 764 | * real module). 765 | */ 766 | unmock(moduleName: string): JestObjectType, 767 | /** 768 | * Instructs Jest to use fake versions of the standard timer functions 769 | * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, 770 | * setImmediate and clearImmediate). 771 | */ 772 | useFakeTimers(): JestObjectType, 773 | /** 774 | * Instructs Jest to use the real versions of the standard timer functions. 775 | */ 776 | useRealTimers(): JestObjectType, 777 | /** 778 | * Creates a mock function similar to jest.fn but also tracks calls to 779 | * object[methodName]. 780 | */ 781 | spyOn(object: Object, methodName: string, accessType?: "get" | "set"): JestMockFn, 782 | /** 783 | * Set the default timeout interval for tests and before/after hooks in milliseconds. 784 | * Note: The default timeout interval is 5 seconds if this method is not called. 785 | */ 786 | setTimeout(timeout: number): JestObjectType 787 | }; 788 | 789 | type JestSpyType = { 790 | calls: JestCallsType 791 | }; 792 | 793 | /** Runs this function after every test inside this context */ 794 | declare function afterEach( 795 | fn: (done: () => void) => ?Promise, 796 | timeout?: number 797 | ): void; 798 | /** Runs this function before every test inside this context */ 799 | declare function beforeEach( 800 | fn: (done: () => void) => ?Promise, 801 | timeout?: number 802 | ): void; 803 | /** Runs this function after all tests have finished inside this context */ 804 | declare function afterAll( 805 | fn: (done: () => void) => ?Promise, 806 | timeout?: number 807 | ): void; 808 | /** Runs this function before any tests have started inside this context */ 809 | declare function beforeAll( 810 | fn: (done: () => void) => ?Promise, 811 | timeout?: number 812 | ): void; 813 | 814 | /** A context for grouping tests together */ 815 | declare var describe: { 816 | /** 817 | * Creates a block that groups together several related tests in one "test suite" 818 | */ 819 | (name: JestTestName, fn: () => void): void, 820 | 821 | /** 822 | * Only run this describe block 823 | */ 824 | only(name: JestTestName, fn: () => void): void, 825 | 826 | /** 827 | * Skip running this describe block 828 | */ 829 | skip(name: JestTestName, fn: () => void): void 830 | }; 831 | 832 | /** An individual test unit */ 833 | declare var it: { 834 | /** 835 | * An individual test unit 836 | * 837 | * @param {JestTestName} Name of Test 838 | * @param {Function} Test 839 | * @param {number} Timeout for the test, in milliseconds. 840 | */ 841 | ( 842 | name: JestTestName, 843 | fn?: (done: () => void) => ?Promise, 844 | timeout?: number 845 | ): void, 846 | /** 847 | * Only run this test 848 | * 849 | * @param {JestTestName} Name of Test 850 | * @param {Function} Test 851 | * @param {number} Timeout for the test, in milliseconds. 852 | */ 853 | only( 854 | name: JestTestName, 855 | fn?: (done: () => void) => ?Promise, 856 | timeout?: number 857 | ): void, 858 | /** 859 | * Skip running this test 860 | * 861 | * @param {JestTestName} Name of Test 862 | * @param {Function} Test 863 | * @param {number} Timeout for the test, in milliseconds. 864 | */ 865 | skip( 866 | name: JestTestName, 867 | fn?: (done: () => void) => ?Promise, 868 | timeout?: number 869 | ): void, 870 | /** 871 | * Run the test concurrently 872 | * 873 | * @param {JestTestName} Name of Test 874 | * @param {Function} Test 875 | * @param {number} Timeout for the test, in milliseconds. 876 | */ 877 | concurrent( 878 | name: JestTestName, 879 | fn?: (done: () => void) => ?Promise, 880 | timeout?: number 881 | ): void 882 | }; 883 | declare function fit( 884 | name: JestTestName, 885 | fn: (done: () => void) => ?Promise, 886 | timeout?: number 887 | ): void; 888 | /** An individual test unit */ 889 | declare var test: typeof it; 890 | /** A disabled group of tests */ 891 | declare var xdescribe: typeof describe; 892 | /** A focused group of tests */ 893 | declare var fdescribe: typeof describe; 894 | /** A disabled individual test */ 895 | declare var xit: typeof it; 896 | /** A disabled individual test */ 897 | declare var xtest: typeof it; 898 | 899 | type JestPrettyFormatColors = { 900 | comment: { close: string, open: string }, 901 | content: { close: string, open: string }, 902 | prop: { close: string, open: string }, 903 | tag: { close: string, open: string }, 904 | value: { close: string, open: string }, 905 | }; 906 | 907 | type JestPrettyFormatIndent = string => string; 908 | type JestPrettyFormatRefs = Array; 909 | type JestPrettyFormatPrint = any => string; 910 | type JestPrettyFormatStringOrNull = string | null; 911 | 912 | type JestPrettyFormatOptions = {| 913 | callToJSON: boolean, 914 | edgeSpacing: string, 915 | escapeRegex: boolean, 916 | highlight: boolean, 917 | indent: number, 918 | maxDepth: number, 919 | min: boolean, 920 | plugins: JestPrettyFormatPlugins, 921 | printFunctionName: boolean, 922 | spacing: string, 923 | theme: {| 924 | comment: string, 925 | content: string, 926 | prop: string, 927 | tag: string, 928 | value: string, 929 | |}, 930 | |}; 931 | 932 | type JestPrettyFormatPlugin = { 933 | print: ( 934 | val: any, 935 | serialize: JestPrettyFormatPrint, 936 | indent: JestPrettyFormatIndent, 937 | opts: JestPrettyFormatOptions, 938 | colors: JestPrettyFormatColors, 939 | ) => string, 940 | test: any => boolean, 941 | }; 942 | 943 | type JestPrettyFormatPlugins = Array; 944 | 945 | /** The expect function is used every time you want to test a value */ 946 | declare var expect: { 947 | /** The object that you want to make assertions against */ 948 | (value: any): JestExpectType & JestPromiseType & EnzymeMatchersType & DomTestingLibraryType & JestJQueryMatchersType & JestExtendedMatchersType, 949 | /** Add additional Jasmine matchers to Jest's roster */ 950 | extend(matchers: { [name: string]: JestMatcher }): void, 951 | /** Add a module that formats application-specific data structures. */ 952 | addSnapshotSerializer(pluginModule: JestPrettyFormatPlugin): void, 953 | assertions(expectedAssertions: number): void, 954 | hasAssertions(): void, 955 | any(value: mixed): JestAsymmetricEqualityType, 956 | anything(): any, 957 | arrayContaining(value: Array): Array, 958 | objectContaining(value: Object): Object, 959 | /** Matches any received string that contains the exact expected string. */ 960 | stringContaining(value: string): string, 961 | stringMatching(value: string | RegExp): string 962 | }; 963 | 964 | // http://jasmine.github.io/2.4/introduction.html#section-Spies 965 | declare function spyOn(value: mixed, method: string): Object; 966 | 967 | /** Holds all functions related to manipulating test runner */ 968 | declare var jest: JestObjectType; 969 | 970 | /** 971 | * The global Jasmine object, this is generally not exposed as the public API, 972 | * using features inside here could break in later versions of Jest. 973 | */ 974 | declare var jasmine: { 975 | DEFAULT_TIMEOUT_INTERVAL: number, 976 | any(value: mixed): JestAsymmetricEqualityType, 977 | anything(): any, 978 | arrayContaining(value: Array): Array, 979 | clock(): JestClockType, 980 | createSpy(name: string): JestSpyType, 981 | createSpyObj( 982 | baseName: string, 983 | methodNames: Array 984 | ): { [methodName: string]: JestSpyType }, 985 | objectContaining(value: Object): Object, 986 | stringMatching(value: string): string 987 | }; 988 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-fusion-app", 3 | "version": "0.1.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "bin": { 7 | "create-fusion-app": "bin/cli.js" 8 | }, 9 | "dependencies": { 10 | "chalk": "^2.4.2", 11 | "fusion-scaffolder": "^0.0.2" 12 | }, 13 | "devDependencies": { 14 | "babel-eslint": "^10.0.1", 15 | "babel-jest": "^24.7.1", 16 | "eslint": "^5.16.0", 17 | "eslint-config-fusion": "^5.0.0", 18 | "eslint-plugin-cup": "^2.0.1", 19 | "eslint-plugin-flowtype": "^3.6.1", 20 | "eslint-plugin-import": "^2.17.2", 21 | "eslint-plugin-jest": "^22.5.0", 22 | "eslint-plugin-prettier": "^3.0.1", 23 | "eslint-plugin-react": "^7.12.4", 24 | "eslint-plugin-react-hooks": "^1.6.0", 25 | "flow-bin": "^0.97.0", 26 | "get-port": "^5.0.0", 27 | "isomorphic-fetch": "^2.2.1", 28 | "jest-cli": "^24.7.1", 29 | "prettier": "^1.17.0", 30 | "puppeteer": "^1.14.0" 31 | }, 32 | "scripts": { 33 | "clean": "rm -rf test-artifacts", 34 | "lint": "eslint .", 35 | "test": "yarn clean && jest" 36 | }, 37 | "jest": { 38 | "cache": false, 39 | "modulePathIgnorePatterns": [ 40 | "templates/.*" 41 | ], 42 | "testEnvironment": "node", 43 | "transform": { 44 | "^.+\\.jsx?$": "babel-jest" 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "uber" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /templates/basic/content/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | extends: [require.resolve('eslint-config-fusion')], 4 | }; 5 | -------------------------------------------------------------------------------- /templates/basic/content/.gitignore: -------------------------------------------------------------------------------- 1 | .fusion 2 | node_modules 3 | 4 | -------------------------------------------------------------------------------- /templates/basic/content/README.md: -------------------------------------------------------------------------------- 1 | # Fusion.js application 2 | 3 | Welcome to your Fusion.js application. Get started by running the application with `yarn dev` in a terminal. 4 | 5 | Visit our documentation at: https://fusionjs.com 6 | -------------------------------------------------------------------------------- /templates/basic/content/flow-typed/globals.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare var __NODE__: boolean; 3 | declare var __BROWSER__: boolean; 4 | declare var __DEV__: boolean; 5 | -------------------------------------------------------------------------------- /templates/basic/content/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-fusion-app", 3 | "version": "0.0.0", 4 | "main": "index.js", 5 | "dependencies": { 6 | "fusion-cli": "^1.18.3", 7 | "fusion-core": "^1.10.6", 8 | "fusion-plugin-react-helmet-async": "^1.0.2", 9 | "fusion-plugin-react-router": "^1.5.4", 10 | "fusion-plugin-styletron-react": "^2.6.1", 11 | "fusion-plugin-universal-events": "^1.3.4", 12 | "fusion-react": "^2.0.0", 13 | "fusion-tokens": "^1.1.1", 14 | "prop-types": "^15.7.2", 15 | "react": "^16.8.6", 16 | "react-dom": "^16.8.6", 17 | "styletron-react": "^4.4.6" 18 | }, 19 | "scripts": { 20 | "dev": "fusion dev", 21 | "lint": "eslint .", 22 | "test": "fusion test", 23 | "build": "fusion build", 24 | "build-production": "fusion build --production", 25 | "start": "fusion start" 26 | }, 27 | "devDependencies": { 28 | "enzyme": "^3.9.0", 29 | "eslint": "^5.16.0", 30 | "eslint-config-fusion": "^5.0.0", 31 | "eslint-plugin-react-hooks": "^1.6.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /templates/basic/content/src/main.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import App from 'fusion-react'; 3 | import Router from 'fusion-plugin-react-router'; 4 | import Styletron from 'fusion-plugin-styletron-react'; 5 | 6 | import root from './root.js'; 7 | 8 | export default () => { 9 | const app = new App(root); 10 | app.register(Styletron); 11 | app.register(Router); 12 | return app; 13 | }; 14 | -------------------------------------------------------------------------------- /templates/basic/content/src/pages/home.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import {styled} from 'fusion-plugin-styletron-react'; 4 | 5 | const Center = styled('div', { 6 | fontFamily: 'HelveticaNeue-Light, Arial', 7 | display: 'flex', 8 | alignItems: 'center', 9 | justifyContent: 'center', 10 | height: '100%', 11 | }); 12 | 13 | const FusionStyle = styled('div', { 14 | fontSize: '80px', 15 | color: 'rgba(0,0,0,.8)', 16 | paddingRight: '30px', 17 | display: 'flex', 18 | }); 19 | 20 | const FullHeightDiv = styled('div', { 21 | height: '100%', 22 | backgroundColor: '#FFFFFF', 23 | }); 24 | 25 | const Circle = styled('div', { 26 | height: '180px', 27 | width: '180px', 28 | marginTop: '20px', 29 | backgroundColor: 'white', 30 | ':hover': {backgroundColor: '#f0f8fa'}, 31 | border: '10px solid #4db5d9', 32 | borderRadius: '50%', 33 | display: 'flex', 34 | justifyContent: 'center', 35 | alignItems: 'center', 36 | }); 37 | 38 | const GettingStartedLink = styled('a', { 39 | textDecoration: 'none', 40 | color: '#4db5d9', 41 | fontSize: '18px', 42 | display: 'flex', 43 | justifyContent: 'center', 44 | alignItems: 'center', 45 | textAlign: 'center', 46 | height: '100%', 47 | }); 48 | 49 | const Home = () => ( 50 | 51 | 60 |
61 | Fusion.js 62 | 63 |
64 | 65 | 66 | Let's Get Started 67 | 68 | 69 |
70 |
71 |
72 | ); 73 | 74 | export default Home; 75 | -------------------------------------------------------------------------------- /templates/basic/content/src/pages/pageNotFound.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import {NotFound} from 'fusion-plugin-react-router'; 4 | 5 | const PageNotFound = () => ( 6 | 7 |
404
8 |
9 | ); 10 | 11 | export default PageNotFound; 12 | -------------------------------------------------------------------------------- /templates/basic/content/src/root.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import {Route, Switch} from 'fusion-plugin-react-router'; 4 | 5 | import Home from './pages/home.js'; 6 | import PageNotFound from './pages/pageNotFound.js'; 7 | 8 | const root = ( 9 | 10 | 11 | 12 | 13 | ); 14 | 15 | export default root; 16 | -------------------------------------------------------------------------------- /templates/basic/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* eslint-env node */ 3 | 4 | module.exports = async function getContext(ctx /*: any*/) { 5 | return ctx; 6 | }; 7 | -------------------------------------------------------------------------------- /test-utils/test-utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* eslint-env jest, node */ 3 | 4 | const {spawn} = require('child_process'); 5 | const getPort = require('get-port'); 6 | const fetch = require('isomorphic-fetch'); 7 | 8 | async function startServer() { 9 | const port = await getPort(); 10 | 11 | // Spin up server 12 | const opts = { 13 | cwd: __dirname + '/../test-artifacts/test-scaffold', 14 | stdio: 'inherit', 15 | env: {...process.env}, 16 | }; 17 | const proc = spawn( 18 | './node_modules/.bin/fusion', 19 | ['dev', '--port', port, '--no-open'], 20 | opts 21 | ); 22 | const stdoutLines = []; 23 | const stderrLines = []; 24 | proc.stdout && 25 | proc.stdout.on('data', (data /*: string*/) => { 26 | stdoutLines.push(data.toString()); 27 | }); 28 | proc.stderr && 29 | proc.stderr.on('data', (data /*: string*/) => { 30 | stderrLines.push(data.toString()); 31 | }); 32 | proc.on('close', (code /*: number*/) => { 33 | if (process.env.VERBOSE) { 34 | const stdout = stdoutLines.join('\n'); 35 | const stderr = stderrLines.join('\n'); 36 | // eslint-disable-next-line no-console 37 | console.log({stdout, stderr, code}); 38 | } 39 | }); 40 | proc.on('error', e => { 41 | // eslint-disable-next-line no-console 42 | console.log(e); 43 | }); 44 | 45 | // Wait for server to start 46 | let started = false; 47 | let numTries = 0; 48 | let res, initialResponse; 49 | while (!started && numTries < 20) { 50 | await new Promise(resolve => setTimeout(resolve, 500)); 51 | try { 52 | res = await fetch(`http://localhost:${port}/`, { 53 | headers: {accept: 'text/html'}, 54 | }); 55 | initialResponse = await res.text(); 56 | 57 | started = true; 58 | } catch (e) { 59 | numTries++; 60 | } 61 | } 62 | if (!started) { 63 | throw new Error('Failed to start server'); 64 | } 65 | return {initialResponse, port, proc}; 66 | } 67 | 68 | module.exports.startServer = startServer; 69 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* eslint-env jest, node */ 3 | 4 | const {promisify} = require('util'); 5 | const exec = promisify(require('child_process').exec); 6 | const {startServer} = require('../test-utils/test-utils.js'); 7 | const puppeteer = require('puppeteer'); 8 | 9 | function log(execOutput) { 10 | // eslint-disable-next-line no-console 11 | console.log(execOutput.stdout); 12 | } 13 | 14 | test('scaffolded app tests pass', async () => { 15 | await exec(`mkdir test-artifacts`); 16 | log( 17 | await exec(`node ../bin/cli.js test-scaffold`, {cwd: './test-artifacts'}) 18 | ); 19 | 20 | const options = {cwd: './test-artifacts/test-scaffold'}; 21 | log(await exec(`yarn build`, options)); 22 | 23 | // Spin up server and validate SSR response 24 | const {port, proc} = await startServer(); 25 | 26 | const browser = await puppeteer.launch({ 27 | args: ['--no-sandbox', '--disable-setuid-sandbox'], 28 | }); 29 | const page = await browser.newPage(); 30 | await page.goto(`http://localhost:${port}`); 31 | const response = await page.content(); 32 | await browser.close(); 33 | expect(response).toContain('Fusion.js'); 34 | 35 | proc.kill(); 36 | }, 300000); 37 | --------------------------------------------------------------------------------