├── .changeset └── config.json ├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── ember-statecharts ├── .eslintignore ├── .eslintrc.js ├── .template-lintrc.js ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── addon-main.js ├── babel.config.json ├── package.json ├── rollup.config.mjs ├── src │ ├── index.ts │ ├── resources │ │ └── statechart.ts │ └── template-registry.ts ├── tsconfig.json └── unpublished-development-types │ └── index.d.ts ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── site ├── .docfy-config.js ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .template-lintrc.js ├── CHANGELOG.md ├── app │ ├── app.js │ ├── components │ │ ├── .gitkeep │ │ ├── api-toc.hbs │ │ ├── api-toc.js │ │ ├── counter-restart.hbs │ │ ├── counter-restart.js │ │ ├── counter.hbs │ │ ├── counter.js │ │ ├── demo.hbs │ │ ├── docs-header.hbs │ │ ├── docs-header.js │ │ ├── docs-toc.hbs │ │ ├── docs-viewer.js │ │ ├── docs │ │ │ ├── quickstart-final.hbs │ │ │ ├── quickstart-final.js │ │ │ ├── quickstart-guide.hbs │ │ │ ├── quickstart-guide.js │ │ │ ├── typescript-usage.hbs │ │ │ └── typescript-usage.js │ │ ├── mobile-docs-toc.hbs │ │ ├── mobile-docs-toc.ts │ │ ├── page-headings.hbs │ │ ├── page-headings.ts │ │ ├── quickstart-button-refined.hbs │ │ ├── quickstart-button-refined.js │ │ ├── quickstart-button.hbs │ │ ├── quickstart-button.js │ │ ├── typed-button.hbs │ │ ├── typed-button.ts │ │ ├── ui-button.hbs │ │ ├── ui-intro.md │ │ ├── ui-loading.hbs │ │ ├── versions-dropdown.hbs │ │ └── versions-dropdown.js │ ├── config │ │ └── environment.d.ts │ ├── controllers │ │ ├── .gitkeep │ │ ├── docs.ts │ │ └── docs │ │ │ └── statecharts.js │ ├── docs │ │ ├── index.md │ │ ├── statecharts.md │ │ └── tutorial.md │ ├── helpers │ │ ├── .gitkeep │ │ └── is-current-version.js │ ├── index.html │ ├── machines │ │ ├── counter-machine.js │ │ ├── quickstart-button-refined.js │ │ ├── quickstart-button.js │ │ └── typed-button.ts │ ├── models │ │ └── .gitkeep │ ├── modifiers │ │ └── intersect-headings.js │ ├── router.js │ ├── routes │ │ ├── .gitkeep │ │ └── application.ts │ ├── snippets │ │ └── quickstart │ │ │ ├── onEntry.js │ │ │ ├── onExit.js │ │ │ └── transition.js │ ├── styles │ │ └── app.css │ ├── templates │ │ ├── application.hbs │ │ ├── docs.hbs │ │ └── index.hbs │ └── utils │ │ └── scroll-to.ts ├── config │ ├── deploy.js │ ├── ember-cli-update.json │ ├── environment.js │ ├── optional-features.json │ └── targets.js ├── ember-cli-build.js ├── lib │ ├── deploy-versioned-doc │ │ ├── index.js │ │ └── package.json │ ├── plausible │ │ ├── index.js │ │ └── package.json │ └── versioned-doc-info.js ├── package.json ├── public │ ├── CNAME │ ├── architect.svg │ ├── discord.svg │ ├── effective-ember.svg │ ├── ember-statecharts-black.svg │ ├── ember-statecharts-white.svg │ ├── harel-paper-1.png │ ├── harel-statechart-paper.png │ ├── quote.svg │ ├── robots.txt │ ├── statecharts-blog.png │ ├── statecharts.svg │ ├── tutorial.png │ ├── xstate-guides.png │ ├── xstate-viz.png │ ├── xstate.svg │ └── youtube.svg ├── tailwind.config.js ├── testem.js ├── tests │ ├── helpers │ │ └── .gitkeep │ ├── index.html │ ├── integration │ │ ├── .gitkeep │ │ └── components │ │ │ └── x-button-test.js │ ├── test-helper.js │ └── unit │ │ └── .gitkeep └── tsconfig.json └── test-apps ├── test-app-ts ├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .template-lintrc.js ├── .watchmanconfig ├── README.md ├── app │ ├── app.js │ ├── components │ │ ├── .gitkeep │ │ ├── use-machine.hbs │ │ └── use-machine.ts │ ├── config │ │ └── environment.d.ts │ ├── controllers │ │ └── .gitkeep │ ├── helpers │ │ └── .gitkeep │ ├── index.html │ ├── machines │ │ └── toggle.ts │ ├── models │ │ └── .gitkeep │ ├── router.js │ ├── routes │ │ └── .gitkeep │ ├── styles │ │ └── app.css │ └── templates │ │ └── application.hbs ├── config │ ├── ember-cli-update.json │ ├── environment.js │ ├── optional-features.json │ └── targets.js ├── ember-cli-build.js ├── package.json ├── public │ └── robots.txt ├── testem.js ├── tests │ ├── helpers │ │ └── index.js │ ├── index.html │ ├── integration │ │ ├── .gitkeep │ │ └── components │ │ │ └── use-machine-test.js │ ├── test-helper.js │ └── unit │ │ └── .gitkeep ├── tsconfig.json ├── types │ ├── ember-data │ │ └── types │ │ │ └── registries │ │ │ └── model.d.ts │ ├── global.d.ts │ └── test-app-ts │ │ └── index.d.ts └── vendor │ └── .gitkeep └── test-app ├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .template-lintrc.js ├── .watchmanconfig ├── README.md ├── app ├── app.js ├── components │ ├── .gitkeep │ ├── delayed-toggle.hbs │ ├── delayed-toggle.js │ ├── self-transition-assign.hbs │ ├── self-transition-assign.js │ ├── self-transition.hbs │ ├── self-transition.js │ ├── use-machine.hbs │ └── use-machine.js ├── controllers │ ├── .gitkeep │ └── application.js ├── helpers │ └── .gitkeep ├── index.html ├── machines │ ├── delayed-toggle.js │ ├── self-transition-assign.js │ ├── self-transition-no-assign.js │ └── toggle.js ├── models │ └── .gitkeep ├── router.js ├── routes │ └── .gitkeep ├── styles │ └── app.css └── templates │ └── application.hbs ├── config ├── ember-cli-update.json ├── ember-try.js ├── environment.js ├── optional-features.json └── targets.js ├── ember-cli-build.js ├── jsconfig.json ├── package.json ├── public └── robots.txt ├── testem.js ├── tests ├── helpers │ └── index.js ├── index.html ├── integration │ ├── .gitkeep │ └── components │ │ ├── delayed-toggle-test.js │ │ └── use-machine-test.js ├── test-helper.js └── unit │ ├── .gitkeep │ └── resources │ └── statechart-test.js └── vendor └── .gitkeep /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | - v2-addon 9 | pull_request: {} 10 | 11 | concurrency: 12 | group: ci-${{ github.head_ref || github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | test: 17 | name: "Tests" 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Install pnpm 23 | uses: pnpm/action-setup@v2 24 | with: 25 | version: 7 26 | - name: Install Node 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: 14.x 30 | cache: pnpm 31 | - name: Install Dependencies 32 | run: pnpm install --frozen-lockfile 33 | - name: Lint 34 | run: pnpm lint 35 | - name: Run Tests 36 | run: pnpm test 37 | 38 | floating: 39 | name: "Floating Dependencies" 40 | runs-on: ubuntu-latest 41 | 42 | steps: 43 | - uses: actions/checkout@v3 44 | - name: Install pnpm 45 | uses: pnpm/action-setup@v2 46 | with: 47 | version: 7 48 | - uses: actions/setup-node@v3 49 | with: 50 | node-version: 14.x 51 | cache: pnpm 52 | - name: Install Dependencies 53 | run: pnpm install --no-lockfile 54 | - name: Run Tests 55 | run: pnpm --filter="test-app" test 56 | 57 | try-scenarios: 58 | name: ${{ matrix.try-scenario }} 59 | runs-on: ubuntu-latest 60 | needs: 'test' 61 | 62 | strategy: 63 | fail-fast: false 64 | matrix: 65 | try-scenario: 66 | - ember-lts-3.28 67 | - ember-lts-4.4 68 | - ember-release 69 | - ember-beta 70 | - ember-canary 71 | - ember-classic 72 | - embroider-safe 73 | - embroider-optimized 74 | 75 | steps: 76 | - uses: actions/checkout@v3 77 | - name: Install pnpm 78 | uses: pnpm/action-setup@v2 79 | with: 80 | version: 7 81 | - name: Install Node 82 | uses: actions/setup-node@v3 83 | with: 84 | node-version: 14.x 85 | cache: pnpm 86 | - name: Install Dependencies 87 | run: pnpm install --frozen-lockfile 88 | - name: Run Tests 89 | run: ./node_modules/.bin/ember try:one ${{ matrix.try-scenario }} 90 | working-directory: test-apps/test-app 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # misc 10 | /.env* 11 | /.pnp* 12 | /.pnpm-debug.log 13 | /.sass-cache 14 | .eslintcache 15 | /connect.lock 16 | /coverage/ 17 | /libpeerconnection.log 18 | /npm-debug.log* 19 | /testem.log 20 | /yarn-error.log 21 | /packages/deploy-site/ 22 | .DS_Store 23 | 24 | # ember-try 25 | /.node_modules.ember-try/ 26 | /package.json.ember-try 27 | /yarn.lock.ember-try 28 | 29 | /deploy-site -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | .lint-todo/ 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | }; 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | * `git clone ` 6 | * `cd ember-statecharts` 7 | * `pnpm install` 8 | 9 | ## Linting 10 | 11 | * `pnpm lint` 12 | * `pnpm lint:fix` 13 | 14 | ## Building the addon 15 | 16 | * `cd ember-statecharts` 17 | * `pnpm build` 18 | 19 | ## Running tests 20 | 21 | * `cd test-app` 22 | * `pnpm test` – Runs the test suite on the current Ember version 23 | * `pnpm test:watch` – Runs the test suite in "watch mode" 24 | 25 | ## Running the test application 26 | 27 | * `cd test-app` 28 | * `pnpm start` 29 | * Visit the test application at [http://localhost:4200](http://localhost:4200). 30 | 31 | For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/). 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-statecharts [![CI](https://github.com/LevelbossMike/ember-statecharts/actions/workflows/ci.yml/badge.svg)](https://github.com/LevelbossMike/ember-statecharts/actions/workflows/ci.yml) [![Ember Observer Score](https://emberobserver.com/badges/ember-statecharts.svg)](https://emberobserver.com/addons/ember-statecharts) 2 | 3 | This addon provides a statechart abstraction based on [XState](https://xstate.js.org/) 4 | for adding statecharts to your Ember.js application. Statecharts can be used to describe 5 | complex behaviour of your objects and separate ui-concern from behavioral concerns 6 | in your applications. This is especially useful in `Ember.Component`-architecture 7 | but can be used across all layers of your application (e.g. when implementing 8 | global application state). 9 | 10 | [View the docs here.](https://ember-statecharts.com) 11 | 12 | ## Compatibility 13 | 14 | * Ember.js v3.28 or above 15 | * Embroider or ember-auto-import v2 16 | 17 | For classic Ember.js-versions pre Ember Octane please use the `0.8.x`-version 18 | of this addon. 19 | 20 | For Ember.js versions `< 3.24` please use the `0.13.x`-version of this addon. 21 | 22 | ## Installation 23 | 24 | 25 | ``` 26 | ember install ember-statecharts 27 | ``` 28 | 29 | ember-statecharts implemens the `useMachine`-resource. You need to install 30 | [ember-resources](https://github.com/NullVoxPopuli/ember-resources) to work with it. 31 | 32 | ``` 33 | ember install ember-resources 34 | ``` 35 | 36 | Because ember-statecharts works with [XState](https://xstate.js.org) internally 37 | you have to install it as a dependency as well. 38 | 39 | ``` 40 | pnpm install -D xstate 41 | ``` 42 | 43 | or 44 | 45 | ``` 46 | yarn add --dev xstate 47 | ``` 48 | 49 | or 50 | 51 | ``` 52 | npm install --save-dev xstate 53 | ``` 54 | 55 | ## Usage 56 | 57 | Statecharts have been around for a long time and have been used to model 58 | stateful, reactive system successfully. You can read about statecharts in the 59 | original paper [Statecharts - A Visual Formalism for Complex 60 | Systems](http://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/resources/statecharts.pdf) 61 | by David Harel. 62 | 63 | With statecharts we finally have a good abstraction to model and discuss behaviour with 64 | other stakeholders of our applications in addition to a design language that 65 | visualizes this behaviour. Here's an example of a button component: 66 | 67 |

68 | button 69 |

70 | 71 | In addition to their modeling capabilities Statecharts are executable and can be used to drive user experience behavior in your Ember.js applications: 72 | 73 | ```js 74 | import Component from '@glimmer/component'; 75 | 76 | import { useMachine } from 'ember-statecharts'; 77 | 78 | import { createMachine } from 'xstate'; 79 | 80 | function noop() {} 81 | 82 | const buttonMachine = createMachine( 83 | { 84 | initial: 'idle', 85 | states: { 86 | idle: { 87 | on: { 88 | SUBMIT: 'busy', 89 | }, 90 | }, 91 | busy: { 92 | invoke: { 93 | src: 'onSubmit', 94 | onDone: 'success', 95 | onError: 'error' 96 | } 97 | }, 98 | success: { 99 | entry: ['onSuccess'], 100 | on: { 101 | SUBMIT: 'busy', 102 | }, 103 | }, 104 | error: { 105 | entry: ['onError'], 106 | on: { 107 | SUBMIT: 'busy', 108 | }, 109 | }, 110 | }, 111 | }, 112 | { 113 | actions: { 114 | onSuccess() {}, 115 | onError() {}, 116 | }, 117 | services: { 118 | onSubmit: async () => {} 119 | } 120 | } 121 | ); 122 | 123 | export default class QuickstartButton extends Component { 124 | statechart = useMachine(this, () => { 125 | const { onSubmit, onSuccess, onError } = this; 126 | 127 | return { 128 | machine: quickstartButtonMachine.withConfig({ 129 | actions: { 130 | onSuccess, 131 | onError, 132 | }, 133 | services: { 134 | onSubmit 135 | } 136 | }), 137 | }; 138 | }); 139 | 140 | get isBusy() { 141 | return this.statechart.state.matches('busy'); 142 | } 143 | 144 | get isDisabled() { 145 | return this.isBusy || this.args.disabled; 146 | } 147 | 148 | handleClick = () => { 149 | this.statechart.send('SUBMIT'); 150 | } 151 | 152 | async onSubmit() { 153 | await (this.args.onSubmit || noop)(); 154 | } 155 | 156 | onSuccess = (_context, { data }) => { 157 | return this.args.onSuccess?(data); 158 | } 159 | 160 | onError = (_context, { data }) => { 161 | return this.args.onError?(data); 162 | } 163 | } 164 | ``` 165 | 166 | Please refer to the [documentation page](http://ember-statecharts.com) for a detailed guide of how you can use statecharts to improve your Ember.js application architecture. 167 | 168 | ## Contributing 169 | 170 | See the [Contributing](CONTRIBUTING.md) guide for details. 171 | 172 | ## License 173 | 174 | This project has been developed by https://www.effective-ember.com/ and [contributors](https://github.com/LevelbossMike/ember-statecharts/graphs/contributors). It is licensed under the [MIT License](LICENSE.md). -------------------------------------------------------------------------------- /ember-statecharts/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /package.json.ember-try 23 | -------------------------------------------------------------------------------- /ember-statecharts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: '@typescript-eslint/parser', 6 | parserOptions: { 7 | ecmaVersion: 'latest', 8 | }, 9 | plugins: ['ember'], 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:ember/recommended', 13 | 'plugin:prettier/recommended', 14 | ], 15 | env: { 16 | browser: true, 17 | }, 18 | rules: {}, 19 | overrides: [ 20 | // ts files 21 | { 22 | files: ['**/*.ts'], 23 | extends: [ 24 | 'plugin:@typescript-eslint/eslint-recommended', 25 | 'plugin:@typescript-eslint/recommended', 26 | ], 27 | rules: { 28 | // Add any custom rules here 29 | }, 30 | }, 31 | // node files 32 | { 33 | files: [ 34 | './.eslintrc.js', 35 | './.prettierrc.js', 36 | './.template-lintrc.js', 37 | './addon-main.js', 38 | ], 39 | parserOptions: { 40 | sourceType: 'script', 41 | }, 42 | env: { 43 | browser: false, 44 | node: true, 45 | }, 46 | plugins: ['node'], 47 | extends: ['plugin:node/recommended'], 48 | }, 49 | ], 50 | }; 51 | -------------------------------------------------------------------------------- /ember-statecharts/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /ember-statecharts/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /ember-statecharts/README.md: -------------------------------------------------------------------------------- 1 | # ember-statecharts [![CI](https://github.com/LevelbossMike/ember-statecharts/actions/workflows/ci.yml/badge.svg)](https://github.com/LevelbossMike/ember-statecharts/actions/workflows/ci.yml) [![Ember Observer Score](https://emberobserver.com/badges/ember-statecharts.svg)](https://emberobserver.com/addons/ember-statecharts) 2 | 3 | This addon provides a statechart abstraction based on [XState](https://xstate.js.org/) 4 | for adding statecharts to your Ember.js application. Statecharts can be used to describe 5 | complex behaviour of your objects and separate ui-concern from behavioral concerns 6 | in your applications. This is especially useful in `Ember.Component`-architecture 7 | but can be used across all layers of your application (e.g. when implementing 8 | global application state). 9 | 10 | [View the docs here.](https://ember-statecharts.com) 11 | 12 | ## Compatibility 13 | 14 | * Ember.js v3.28 or above 15 | * Embroider or ember-auto-import v2 16 | 17 | For classic Ember.js-versions pre Ember Octane please use the `0.8.x`-version 18 | of this addon. 19 | 20 | For Ember.js versions `< 3.24` please use the `0.13.x`-version of this addon. 21 | 22 | ## Installation 23 | 24 | 25 | ``` 26 | ember install ember-statecharts 27 | ``` 28 | 29 | ember-statecharts implemens the `useMachine`-resource. You need to install 30 | [ember-resources](https://github.com/NullVoxPopuli/ember-resources) to work with it. 31 | 32 | ``` 33 | ember install ember-resources 34 | ``` 35 | 36 | Because ember-statecharts works with [XState](https://xstate.js.org) internally 37 | you have to install it as a dependency as well. 38 | 39 | ``` 40 | pnpm install -D xstate 41 | ``` 42 | 43 | or 44 | 45 | ``` 46 | yarn add --dev xstate 47 | ``` 48 | 49 | or 50 | 51 | ``` 52 | npm install --save-dev xstate 53 | ``` 54 | 55 | ## Usage 56 | 57 | Statecharts have been around for a long time and have been used to model 58 | stateful, reactive system successfully. You can read about statecharts in the 59 | original paper [Statecharts - A Visual Formalism for Complex 60 | Systems](http://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/resources/statecharts.pdf) 61 | by David Harel. 62 | 63 | With statecharts we finally have a good abstraction to model and discuss behaviour with 64 | other stakeholders of our applications in addition to a design language that 65 | visualizes this behaviour. Here's an example of a button component: 66 | 67 |

68 | button 69 |

70 | 71 | In addition to their modeling capabilities Statecharts are executable and can be used to drive user experience behavior in your Ember.js applications: 72 | 73 | ```js 74 | import Component from '@glimmer/component'; 75 | 76 | import { useMachine } from 'ember-statecharts'; 77 | 78 | import { createMachine } from 'xstate'; 79 | 80 | function noop() {} 81 | 82 | const buttonMachine = createMachine( 83 | { 84 | initial: 'idle', 85 | states: { 86 | idle: { 87 | on: { 88 | SUBMIT: 'busy', 89 | }, 90 | }, 91 | busy: { 92 | invoke: { 93 | src: 'onSubmit', 94 | onDone: 'success', 95 | onError: 'error' 96 | } 97 | }, 98 | success: { 99 | entry: ['onSuccess'], 100 | on: { 101 | SUBMIT: 'busy', 102 | }, 103 | }, 104 | error: { 105 | entry: ['onError'], 106 | on: { 107 | SUBMIT: 'busy', 108 | }, 109 | }, 110 | }, 111 | }, 112 | { 113 | actions: { 114 | onSuccess() {}, 115 | onError() {}, 116 | }, 117 | services: { 118 | onSubmit: async () => {} 119 | } 120 | } 121 | ); 122 | 123 | export default class QuickstartButton extends Component { 124 | statechart = useMachine(this, () => { 125 | const { onSubmit, onSuccess, onError } = this; 126 | 127 | return { 128 | machine: quickstartButtonMachine.withConfig({ 129 | actions: { 130 | onSuccess, 131 | onError, 132 | }, 133 | services: { 134 | onSubmit 135 | } 136 | }), 137 | }; 138 | }); 139 | 140 | get isBusy() { 141 | return this.statechart.state.matches('busy'); 142 | } 143 | 144 | get isDisabled() { 145 | return this.isBusy || this.args.disabled; 146 | } 147 | 148 | handleClick = () => { 149 | this.statechart.send('SUBMIT'); 150 | } 151 | 152 | async onSubmit() { 153 | await (this.args.onSubmit || noop)(); 154 | } 155 | 156 | onSuccess = (_context, { data }) => { 157 | return this.args.onSuccess?(data); 158 | } 159 | 160 | onError = (_context, { data }) => { 161 | return this.args.onError?(data); 162 | } 163 | } 164 | ``` 165 | 166 | Please refer to the [documentation page](http://ember-statecharts.com) for a detailed guide of how you can use statecharts to improve your Ember.js application architecture. 167 | 168 | ## Contributing 169 | 170 | See the [Contributing](CONTRIBUTING.md) guide for details. 171 | 172 | ## License 173 | 174 | This project has been developed by https://www.effective-ember.com/ and [contributors](https://github.com/LevelbossMike/ember-statecharts/graphs/contributors). It is licensed under the [MIT License](LICENSE.md). -------------------------------------------------------------------------------- /ember-statecharts/addon-main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { addonV1Shim } = require('@embroider/addon-shim'); 4 | module.exports = addonV1Shim(__dirname); 5 | -------------------------------------------------------------------------------- /ember-statecharts/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-typescript"]], 3 | "plugins": [ 4 | "@embroider/addon-dev/template-colocation-plugin", 5 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 6 | "@babel/plugin-proposal-class-properties" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /ember-statecharts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-statecharts", 3 | "version": "0.16.1", 4 | "description": "Statecharts for Ember.js applications", 5 | "keywords": [ 6 | "ember-addon" 7 | ], 8 | "repository": "https://github.com/LevelbossMike/ember-statecharts", 9 | "license": "MIT", 10 | "author": "", 11 | "files": [ 12 | "addon-main.js", 13 | "dist" 14 | ], 15 | "scripts": { 16 | "build": "rollup --config", 17 | "lint": "concurrently 'npm:lint:*(!fix)' --names 'lint:'", 18 | "lint:fix": "concurrently 'npm:lint:*:fix' --names 'fix:'", 19 | "lint:hbs": "ember-template-lint . --no-error-on-unmatched-pattern", 20 | "lint:js": "eslint . --cache", 21 | "lint:hbs:fix": "ember-template-lint . --fix --no-error-on-unmatched-pattern", 22 | "lint:js:fix": "eslint . --fix", 23 | "lint:types": "glint", 24 | "start": "rollup --config --watch", 25 | "test": "echo 'A v2 addon does not have tests, run tests in test-app'", 26 | "prepack": "rollup --config" 27 | }, 28 | "dependencies": { 29 | "@embroider/addon-shim": "^1.0.0" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.17.0", 33 | "@babel/plugin-proposal-class-properties": "^7.16.7", 34 | "@babel/plugin-proposal-decorators": "^7.17.0", 35 | "@babel/plugin-syntax-decorators": "^7.17.0", 36 | "@babel/preset-typescript": "^7.18.6", 37 | "@babel/runtime": "^7.20.1", 38 | "@embroider/addon-dev": "^2.0.0", 39 | "@glimmer/tracking": "^1.1.2", 40 | "@glint/core": "^0.9.7", 41 | "@glint/environment-ember-loose": "^0.9.7", 42 | "@tsconfig/ember": "^1.0.1", 43 | "@types/ember": "^4.0.0", 44 | "@types/ember__application": "^4.0.0", 45 | "@types/ember__array": "^4.0.0", 46 | "@types/ember__component": "^4.0.0", 47 | "@types/ember__controller": "^4.0.0", 48 | "@types/ember__debug": "^4.0.0", 49 | "@types/ember__destroyable": "^4.0.0", 50 | "@types/ember__engine": "^4.0.0", 51 | "@types/ember__error": "^4.0.0", 52 | "@types/ember__object": "^4.0.0", 53 | "@types/ember__owner": "^4.0.0", 54 | "@types/ember__polyfills": "^4.0.0", 55 | "@types/ember__routing": "^4.0.0", 56 | "@types/ember__runloop": "^4.0.0", 57 | "@types/ember__service": "^4.0.0", 58 | "@types/ember__string": "^3.16.0", 59 | "@types/ember__template": "^4.0.0", 60 | "@types/ember__test": "^4.0.0", 61 | "@types/ember__test-helpers": "^2.6.1", 62 | "@types/ember__utils": "^4.0.0", 63 | "@typescript-eslint/eslint-plugin": "^5.30.5", 64 | "@typescript-eslint/parser": "^5.30.5", 65 | "concurrently": "^7.2.1", 66 | "ember-resources": "^5.6.0", 67 | "ember-template-lint": "^4.0.0", 68 | "eslint": "^7.32.0", 69 | "eslint-config-prettier": "^8.3.0", 70 | "eslint-plugin-ember": "^10.5.8", 71 | "eslint-plugin-node": "^11.1.0", 72 | "eslint-plugin-prettier": "^4.0.0", 73 | "prettier": "^2.5.1", 74 | "rollup": "^2.67.0", 75 | "rollup-plugin-copy": "^3.4.0", 76 | "rollup-plugin-ts": "^3.0.2", 77 | "typescript": "^4.7.4", 78 | "xstate": "^4.34.0" 79 | }, 80 | "publishConfig": { 81 | "registry": "https://registry.npmjs.org" 82 | }, 83 | "ember": { 84 | "edition": "octane" 85 | }, 86 | "ember-addon": { 87 | "version": 2, 88 | "type": "addon", 89 | "main": "addon-main.js", 90 | "app-js": {} 91 | }, 92 | "exports": { 93 | ".": "./dist/index.js", 94 | "./*": { 95 | "types": "./dist/*.d.ts", 96 | "default": "./dist/*.js" 97 | }, 98 | "./addon-main.js": "./addon-main.js" 99 | }, 100 | "typesVersions": { 101 | "*": { 102 | "*": [ 103 | "dist/*" 104 | ] 105 | } 106 | }, 107 | "peerDependencies": { 108 | "@glimmer/tracking": "^1.1.2", 109 | "ember-resources": "^5.6.0", 110 | "xstate": "^4.34.0" 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /ember-statecharts/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-ts'; 2 | import copy from 'rollup-plugin-copy'; 3 | import { Addon } from '@embroider/addon-dev/rollup'; 4 | 5 | const addon = new Addon({ 6 | srcDir: 'src', 7 | destDir: 'dist', 8 | }); 9 | 10 | export default { 11 | // This provides defaults that work well alongside `publicEntrypoints` below. 12 | // You can augment this if you need to. 13 | output: addon.output(), 14 | 15 | plugins: [ 16 | // These are the modules that users should be able to import from your 17 | // addon. Anything not listed here may get optimized away. 18 | addon.publicEntrypoints(['components/**/*.js', 'index.js', 'template-registry.js']), 19 | 20 | // These are the modules that should get reexported into the traditional 21 | // "app" tree. Things in here should also be in publicEntrypoints above, but 22 | // not everything in publicEntrypoints necessarily needs to go here. 23 | addon.appReexports(['components/**/*.js']), 24 | 25 | // compile TypeScript to latest JavaScript, including Babel transpilation 26 | typescript({ 27 | transpiler: 'babel', 28 | browserslist: false, 29 | transpileOnly: false, 30 | }), 31 | 32 | // Follow the V2 Addon rules about dependencies. Your code can import from 33 | // `dependencies` and `peerDependencies` as well as standard Ember-provided 34 | // package names. 35 | addon.dependencies(), 36 | 37 | // Ensure that standalone .hbs files are properly integrated as Javascript. 38 | addon.hbs(), 39 | 40 | // addons are allowed to contain imports of .css files, which we want rollup 41 | // to leave alone and keep in the published output. 42 | addon.keepAssets(['**/*.css']), 43 | 44 | // Remove leftover build artifacts when starting a new build. 45 | addon.clean(), 46 | 47 | // Copy Readme and License into published package 48 | copy({ 49 | targets: [ 50 | { src: '../README.md', dest: '.' }, 51 | { src: '../LICENSE.md', dest: '.' }, 52 | ], 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /ember-statecharts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { useMachine, Statechart } from './resources/statechart'; 2 | 3 | export { useMachine, Statechart }; 4 | -------------------------------------------------------------------------------- /ember-statecharts/src/resources/statechart.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from 'ember-resources'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { registerDestructor } from '@ember/destroyable'; 4 | import { action } from '@ember/object'; 5 | import { getOwner } from '@ember/application'; 6 | import { later, cancel } from '@ember/runloop'; 7 | import { 8 | Event, 9 | EventData, 10 | interpret, 11 | SCXML, 12 | SingleOrArray, 13 | StateFrom, 14 | StateValueFrom, 15 | } from 'xstate'; 16 | import type { 17 | AreAllImplementationsAssumedToBeProvided, 18 | Interpreter, 19 | StateMachine, 20 | EventObject, 21 | Typestate, 22 | ResolveTypegenMeta, 23 | StateSchema, 24 | TypegenDisabled, 25 | NoInfer, 26 | BaseActionObject, 27 | ServiceMap, 28 | InterpreterOptions, 29 | } from 'xstate'; 30 | 31 | import type Owner from '@ember/owner'; 32 | 33 | import type { ExpandArgs } from 'ember-resources'; 34 | 35 | interface StatechartArgs< 36 | TContext, 37 | TStateSchema extends StateSchema, 38 | TEvent extends EventObject, 39 | TAction extends BaseActionObject, 40 | TServiceMap extends ServiceMap, 41 | TTypestate extends Typestate = { 42 | value: any; 43 | context: TContext; 44 | }, 45 | TResolvedTypesMeta = ResolveTypegenMeta< 46 | TypegenDisabled, 47 | NoInfer, 48 | TAction, 49 | TServiceMap 50 | > 51 | > { 52 | positional: []; 53 | named: { 54 | machine: AreAllImplementationsAssumedToBeProvided extends true 55 | ? StateMachine< 56 | TContext, 57 | TStateSchema, 58 | TEvent, 59 | TTypestate, 60 | TAction, 61 | TServiceMap, 62 | TResolvedTypesMeta 63 | > 64 | : 'Some implementations missing'; 65 | update?: (opts: { 66 | machine: AreAllImplementationsAssumedToBeProvided extends true 67 | ? StateMachine< 68 | TContext, 69 | TStateSchema, 70 | TEvent, 71 | TTypestate, 72 | TAction, 73 | TServiceMap, 74 | TResolvedTypesMeta 75 | > 76 | : 'Some implementations missing'; 77 | restart: () => void; 78 | send: Interpreter< 79 | TContext, 80 | TStateSchema, 81 | TEvent, 82 | TTypestate, 83 | TResolvedTypesMeta 84 | >['send']; 85 | }) => void; 86 | initialState?: StateValueFrom< 87 | StateMachine< 88 | TContext, 89 | TStateSchema, 90 | TEvent, 91 | TTypestate, 92 | TAction, 93 | TServiceMap, 94 | TResolvedTypesMeta 95 | > 96 | >; 97 | onTransition?: ( 98 | state: StateFrom< 99 | StateMachine< 100 | TContext, 101 | TStateSchema, 102 | TEvent, 103 | TTypestate, 104 | TAction, 105 | TServiceMap, 106 | TResolvedTypesMeta 107 | > 108 | > 109 | ) => void; 110 | interpreterOptions?: InterpreterOptions; 111 | }; 112 | } 113 | 114 | export class Statechart< 115 | TContext, 116 | TStateSchema extends StateSchema, 117 | TEvent extends EventObject, 118 | TAction extends BaseActionObject, 119 | TServiceMap extends ServiceMap, 120 | TTypestate extends Typestate = { 121 | value: any; 122 | context: TContext; 123 | }, 124 | TResolvedTypesMeta = ResolveTypegenMeta< 125 | TypegenDisabled, 126 | NoInfer, 127 | TAction, 128 | TServiceMap 129 | > 130 | > extends Resource< 131 | StatechartArgs< 132 | TContext, 133 | TStateSchema, 134 | TEvent, 135 | TAction, 136 | TServiceMap, 137 | TTypestate, 138 | TResolvedTypesMeta 139 | > 140 | > { 141 | @tracked state: 142 | | StateFrom< 143 | StateMachine< 144 | TContext, 145 | TStateSchema, 146 | TEvent, 147 | TTypestate, 148 | TAction, 149 | TServiceMap, 150 | TResolvedTypesMeta 151 | > 152 | > 153 | | undefined; 154 | 155 | #interpreter: 156 | | Interpreter< 157 | TContext, 158 | TStateSchema, 159 | TEvent, 160 | TTypestate, 161 | TResolvedTypesMeta 162 | > 163 | | undefined; 164 | 165 | #interpreters: Array< 166 | Interpreter 167 | > = []; 168 | 169 | constructor(owner: Owner | unknown) { 170 | super(owner); 171 | 172 | // cleanup interpreter 173 | registerDestructor(this, () => this.#interpreter?.stop()); 174 | } 175 | 176 | get config() { 177 | const owner = getOwner(this); 178 | if (owner) { 179 | // eslint-disable-next-line 180 | // @ts-ignore 181 | const config = owner.resolveRegistration('config:environment') as { 182 | [key: string]: unknown; 183 | }; 184 | 185 | const statechartConfig = config['ember-statecharts'] as { 186 | runloopClockService: boolean; 187 | }; 188 | 189 | if (statechartConfig) { 190 | return statechartConfig; 191 | } 192 | } 193 | return { 194 | runloopClockService: false, 195 | }; 196 | } 197 | 198 | get _defaultInterpreterOptions() { 199 | if (this.config.runloopClockService) { 200 | return { 201 | clock: { 202 | setTimeout(fn, timeout) { 203 | later(fn, timeout); 204 | }, 205 | clearTimeout(id) { 206 | cancel(id); 207 | }, 208 | }, 209 | }; 210 | } else { 211 | return {}; 212 | } 213 | } 214 | 215 | modify( 216 | positional: [], 217 | named: ExpandArgs< 218 | StatechartArgs< 219 | TContext, 220 | TStateSchema, 221 | TEvent, 222 | TAction, 223 | TServiceMap, 224 | TTypestate, 225 | TResolvedTypesMeta 226 | > 227 | >['Named'] 228 | ): void { 229 | if (!this.#interpreter) { 230 | this._setupInterpreter(named); 231 | } else { 232 | if (named.update) { 233 | named.update.call(null, { 234 | machine: named.machine, 235 | restart: (initialState?: typeof named['initialState']) => { 236 | const opts = { 237 | ...named, 238 | }; 239 | 240 | if (initialState) { 241 | opts.initialState = initialState; 242 | } 243 | 244 | this._setupInterpreter(opts); 245 | }, 246 | send: this.#interpreter.send, 247 | }); 248 | } 249 | } 250 | } 251 | 252 | @action send( 253 | event: SCXML.Event | SingleOrArray>, 254 | payload?: EventData | undefined 255 | ) { 256 | return this.#interpreter?.send(event, payload); 257 | } 258 | 259 | _setupInterpreter( 260 | named: ExpandArgs< 261 | StatechartArgs< 262 | TContext, 263 | TStateSchema, 264 | TEvent, 265 | TAction, 266 | TServiceMap, 267 | TTypestate, 268 | TResolvedTypesMeta 269 | > 270 | >['Named'] 271 | ) { 272 | const { machine, initialState, onTransition, interpreterOptions } = named; 273 | 274 | const _interpreterOptions = 275 | interpreterOptions || this._defaultInterpreterOptions; 276 | 277 | const interpreter = interpret(machine, { 278 | ..._interpreterOptions, 279 | }).onTransition((state) => { 280 | if (state.changed || state.changed === undefined) { 281 | this.state = state; 282 | } 283 | }); 284 | 285 | if (onTransition) { 286 | interpreter.onTransition(onTransition); 287 | } 288 | 289 | this.#interpreters = [...this.#interpreters, interpreter]; 290 | 291 | interpreter.start(initialState || interpreter.machine.initialState); 292 | 293 | this.#interpreter = interpreter; 294 | 295 | this._stopOldInterpreter(); 296 | } 297 | 298 | _stopOldInterpreter() { 299 | const beforeLastInterpreter = 300 | this.#interpreters[this.#interpreters.length - 2]; 301 | 302 | if (beforeLastInterpreter) { 303 | beforeLastInterpreter.stop(); 304 | } 305 | } 306 | } 307 | 308 | export function useMachine< 309 | TContext, 310 | TStateSchema extends StateSchema, 311 | TEvent extends EventObject, 312 | TAction extends BaseActionObject, 313 | TServiceMap extends ServiceMap, 314 | TTypestate extends Typestate = { 315 | value: any; 316 | context: TContext; 317 | }, 318 | TResolvedTypesMeta = ResolveTypegenMeta< 319 | TypegenDisabled, 320 | NoInfer, 321 | TAction, 322 | TServiceMap 323 | > 324 | >( 325 | context: unknown, 326 | options: () => ExpandArgs< 327 | StatechartArgs< 328 | TContext, 329 | TStateSchema, 330 | TEvent, 331 | TAction, 332 | TServiceMap, 333 | TTypestate, 334 | TResolvedTypesMeta 335 | > 336 | >['Named'] 337 | ) { 338 | return Statechart.from< 339 | Statechart< 340 | TContext, 341 | TStateSchema, 342 | TEvent, 343 | TAction, 344 | TServiceMap, 345 | TTypestate, 346 | TResolvedTypesMeta 347 | > 348 | >(context, options); 349 | } 350 | -------------------------------------------------------------------------------- /ember-statecharts/src/template-registry.ts: -------------------------------------------------------------------------------- 1 | // Easily allow apps, which are not yet using strict mode templates, to consume your Glint types, by importing this file. 2 | // Add all your components, helpers and modifiers to the template registry here, so apps don't have to do this. 3 | // See https://typed-ember.gitbook.io/glint/using-glint/ember/authoring-addons 4 | 5 | // import type MyComponent from './components/my-component'; 6 | 7 | // Remove this once entries have been added! 👇 8 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 9 | export default interface Registry { 10 | // MyComponent: typeof MyComponent 11 | } 12 | -------------------------------------------------------------------------------- /ember-statecharts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember/tsconfig.json", 3 | "include": [ 4 | "src/**/*", 5 | "unpublished-development-types/**/*" 6 | ], 7 | "glint": { 8 | "environment": "ember-loose" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ember-statecharts/unpublished-development-types/index.d.ts: -------------------------------------------------------------------------------- 1 | // Add any types here that you need for local development only. 2 | // These will *not* be published as part of your addon, so be careful that your published code does not rely on them! 3 | 4 | import '@glint/environment-ember-loose'; 5 | 6 | declare module '@glint/environment-ember-loose/registry' { 7 | // Remove this once entries have been added! 👇 8 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 9 | export default interface Registry { 10 | // Add any registry entries from other addons here that your addon itself uses (in non-strict mode templates) 11 | // See https://typed-ember.gitbook.io/glint/using-glint/ember/using-addons 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-statecharts", 3 | "private": true, 4 | "repository": "", 5 | "license": "MIT", 6 | "author": "", 7 | "scripts": { 8 | "prepare": "pnpm build", 9 | "build": "pnpm --filter ember-statecharts build", 10 | "start": "concurrently 'npm:start:*' --restart-after 5000 --prefix-colors cyan,white,yellow", 11 | "start:tests": "pnpm --filter test-app start", 12 | "start:test-ts": "pnpm --filter test-app-ts start --port=0", 13 | "start:addon": "pnpm --filter ember-statecharts start --no-watch.clearScreen", 14 | "test": "pnpm --filter='test-app*' test", 15 | "lint": "pnpm --filter '*' lint", 16 | "lint:fix": "pnpm --filter '*' lint:fix" 17 | }, 18 | "devDependencies": { 19 | "concurrently": "^7.2.1", 20 | "prettier": "^2.5.1" 21 | }, 22 | "dependencies": { 23 | "@changesets/cli": "^2.25.2" 24 | } 25 | } -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'ember-statecharts' 3 | - 'site' 4 | - 'test-apps/*' 5 | -------------------------------------------------------------------------------- /site/.docfy-config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable node/no-missing-require */ 2 | const path = require('path'); 3 | const autolinkHeadings = require('remark-autolink-headings'); 4 | const docfyWithProse = require('@docfy/plugin-with-prose'); 5 | const prism = require('@mapbox/rehype-prism'); 6 | const refractor = require('refractor'); 7 | 8 | refractor.alias('handlebars', 'hbs'); 9 | refractor.alias('shell', 'sh'); 10 | 11 | module.exports = { 12 | tocMaxDepth: 3, 13 | sources: [ 14 | { 15 | root: path.resolve(__dirname, './app/docs'), 16 | pattern: '**/*.md', 17 | urlPrefix: 'docs', 18 | }, 19 | ], 20 | remarkPlugins: [ 21 | [ 22 | autolinkHeadings, 23 | { 24 | behavior: 'wrap', 25 | }, 26 | ], 27 | ], 28 | plugins: [docfyWithProse], 29 | rehypePlugins: [prism], 30 | labels: { 31 | components: 'Components', 32 | workflow: 'Workflow', 33 | docs: 'Documentation', 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /site/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /**/dist/ 7 | /**/tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /package.json.ember-try 23 | 24 | # api-docs 25 | /public/api-docs/ 26 | -------------------------------------------------------------------------------- /site/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | ecmaVersion: 2018, 8 | sourceType: 'module', 9 | ecmaFeatures: { 10 | legacyDecorators: true, 11 | }, 12 | }, 13 | plugins: ['ember'], 14 | extends: [ 15 | 'eslint:recommended', 16 | 'plugin:ember/recommended', 17 | 'plugin:prettier/recommended', 18 | ], 19 | env: { 20 | browser: true, 21 | }, 22 | rules: {}, 23 | overrides: [ 24 | // node files 25 | { 26 | files: [ 27 | './.eslintrc.js', 28 | './.prettierrc.js', 29 | './.template-lintrc.js', 30 | './ember-cli-build.js', 31 | './index.js', 32 | './testem.js', 33 | './blueprints/*/index.js', 34 | './config/**/*.js', 35 | './tests/dummy/config/**/*.js', 36 | './.docfy-config.js', 37 | './tailwind.config.js', 38 | ], 39 | parserOptions: { 40 | sourceType: 'script', 41 | }, 42 | env: { 43 | browser: false, 44 | node: true, 45 | }, 46 | plugins: ['node'], 47 | extends: ['plugin:node/recommended'], 48 | rules: { 49 | // this can be removed once the following is fixed 50 | // https://github.com/mysticatea/eslint-plugin-node/issues/77 51 | 'node/no-unpublished-require': 'off', 52 | }, 53 | }, 54 | { 55 | // Test files: 56 | files: ['tests/**/*-test.{js,ts}'], 57 | extends: ['plugin:qunit/recommended'], 58 | }, 59 | ], 60 | }; 61 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /.eslintcache 16 | /*.tsbuildinfo 17 | /connect.lock 18 | /coverage/ 19 | /libpeerconnection.log 20 | /npm-debug.log* 21 | /testem.log 22 | /yarn-error.log 23 | 24 | # ember-try 25 | /.node_modules.ember-try/ 26 | /bower.json.ember-try 27 | /package.json.ember-try 28 | 29 | .DS_Store 30 | # generated api-docs 31 | /public/api-docs/ 32 | -------------------------------------------------------------------------------- /site/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | 18 | # ember-try 19 | /.node_modules.ember-try/ 20 | /bower.json.ember-try 21 | /package.json.ember-try 22 | 23 | # api-docs 24 | /public/api-docs/ 25 | -------------------------------------------------------------------------------- /site/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /site/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # site 2 | 3 | ## 0.13.6 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [1997100] 8 | - ember-statecharts@0.16.1 9 | 10 | ## 0.13.5 11 | 12 | ### Patch Changes 13 | 14 | - Updated dependencies [9ee0ef3] 15 | - ember-statecharts@0.16.0 16 | 17 | ## 0.13.4 18 | 19 | ### Patch Changes 20 | 21 | - Updated dependencies [da688ce] 22 | - ember-statecharts@0.15.1 23 | 24 | ## 0.13.3 25 | 26 | ### Patch Changes 27 | 28 | - Updated dependencies [1c27cb6] 29 | - ember-statecharts@0.15.0 30 | -------------------------------------------------------------------------------- /site/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'site/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /site/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/app/components/.gitkeep -------------------------------------------------------------------------------- /site/app/components/api-toc.hbs: -------------------------------------------------------------------------------- 1 | 57 | -------------------------------------------------------------------------------- /site/app/components/api-toc.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import config from 'site/config/environment'; 3 | 4 | export default class ApiTocComponent extends Component { 5 | get rootUrl() { 6 | return config.rootURL; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /site/app/components/counter-restart.hbs: -------------------------------------------------------------------------------- 1 |
2 | Counter Count: {{this.statechart.state.context.count}} 3 |
4 |
5 |
6 | {{#if this.isActive}} 7 | 8 | Deactivate 9 | 10 | {{else}} 11 | 12 | Activate 13 | 14 | {{/if}} 15 |
16 |
17 | 21 | - 22 | 23 | 27 | + 28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /site/app/components/counter-restart.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET counter-update-restart 2 | import Component from '@glimmer/component'; 3 | import { action } from '@ember/object'; 4 | import { useMachine } from 'ember-statecharts'; 5 | import CounterMachine from '../machines/counter-machine'; 6 | 7 | export default class CounterComponent extends Component { 8 | statechart = useMachine(this, () => { 9 | return { 10 | machine: CounterMachine.withContext({ 11 | count: this.args.count, 12 | }), 13 | update: ({ restart }) => { 14 | restart(); 15 | }, 16 | }; 17 | }); 18 | 19 | get isActive() { 20 | return this.statechart.state.matches('active'); 21 | } 22 | 23 | get isDisabled() { 24 | return this.statechart.state.matches('inactive'); 25 | } 26 | 27 | @action decrement() { 28 | this.statechart.send('DECREMENT'); 29 | } 30 | 31 | @action increment() { 32 | this.statechart.send('INCREMENT'); 33 | } 34 | 35 | @action activate() { 36 | this.statechart.send('ACTIVATE'); 37 | } 38 | 39 | @action deactivate() { 40 | this.statechart.send('DEACTIVATE'); 41 | } 42 | } 43 | // END-SNIPPET 44 | -------------------------------------------------------------------------------- /site/app/components/counter.hbs: -------------------------------------------------------------------------------- 1 |
2 | Counter Count: {{this.statechart.state.context.count}} 3 |
4 |
5 |
6 | {{#if this.isActive}} 7 | 8 | Deactivate 9 | 10 | {{else}} 11 | 12 | Activate 13 | 14 | {{/if}} 15 |
16 |
17 | 21 | - 22 | 23 | 27 | + 28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /site/app/components/counter.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET counter-update-event 2 | import Component from '@glimmer/component'; 3 | import { action } from '@ember/object'; 4 | import { useMachine } from 'ember-statecharts'; 5 | import CounterMachine from '../machines/counter-machine'; 6 | 7 | export default class CounterComponent extends Component { 8 | statechart = useMachine(this, () => { 9 | const machine = CounterMachine.withContext({ 10 | count: this.args.count, 11 | }); 12 | 13 | return { 14 | machine, 15 | update: ({ send }) => { 16 | send('RESET_COUNT', { count: machine.context.count }); 17 | }, 18 | }; 19 | }); 20 | 21 | get isActive() { 22 | return this.statechart.state.matches('active'); 23 | } 24 | 25 | get isDisabled() { 26 | return this.statechart.state.matches('inactive'); 27 | } 28 | 29 | @action decrement() { 30 | this.statechart.send('DECREMENT'); 31 | } 32 | 33 | @action increment() { 34 | this.statechart.send('INCREMENT'); 35 | } 36 | 37 | @action activate() { 38 | this.statechart.send('ACTIVATE'); 39 | } 40 | 41 | @action deactivate() { 42 | this.statechart.send('DEACTIVATE'); 43 | } 44 | } 45 | // END-SNIPPET 46 | -------------------------------------------------------------------------------- /site/app/components/demo.hbs: -------------------------------------------------------------------------------- 1 | 2 |
6 |
7 | {{yield (hash ui=(hash useSnippet=showcase.ui.useSnippet))}} 8 |
9 |
10 |
11 | {{#each showcase.state.snippets as |snippet|}} 12 | 28 | {{/each}} 29 |
30 | {{#if showcase.state.activeSnippet}} 31 | 36 | {{/if}} 37 |
38 |
39 |
40 | -------------------------------------------------------------------------------- /site/app/components/docs-header.hbs: -------------------------------------------------------------------------------- 1 | {{!-- template-lint-disable --}} 2 |
3 |
4 | 5 | {{#docs-header/link "index"}} 6 | 7 | {{svg-jar "statecharts" width=48 height=48 class="docs-fill-none"}} 8 | 9 | {{/docs-header/link}} 10 | 11 |
12 | {{#docs-header/link "docs"}} 13 | Documentation 14 | {{/docs-header/link}} 15 | 16 | {{yield (hash 17 | link=(component "docs-header/link") 18 | )}} 19 | 20 |
21 | 22 | {{#docs-header/link on-click=(action (toggle "isShowingVersionSelector" this))}} 23 | 24 | 25 | {{#if (eq currentVersion.key latestVersionName)}} 26 | {{#if currentVersion.tag}} 27 | {{currentVersion.tag}} 28 | {{else}} 29 | Latest 30 | {{/if}} 31 | {{else}} 32 | {{currentVersion.name}} 33 | {{/if}} 34 | 35 | {{svg-jar "caret" height=12 width=12}} 36 | 37 | {{/docs-header/link}} 38 | 39 | {{#if projectHref}} 40 | {{#docs-header/link href=projectHref}} 41 | 42 | {{svg-jar "github" width=24 height=24}} 43 | 44 | {{/docs-header/link}} 45 | {{/if}} 46 | 47 | {{!-- Something to take up space on mobile, so the scrolling nav isn't hugging the edge --}} 48 |
49 |
50 |
51 |
52 | 53 | {{#if isShowingVersionSelector}} 54 | {{docs-header/version-selector on-close=(action (mut isShowingVersionSelector false))}} 55 | {{/if}} 56 | -------------------------------------------------------------------------------- /site/app/components/docs-header.js: -------------------------------------------------------------------------------- 1 | import DocsHeader from 'ember-cli-addon-docs/components/docs-header/component'; 2 | 3 | export default DocsHeader; 4 | -------------------------------------------------------------------------------- /site/app/components/docs-toc.hbs: -------------------------------------------------------------------------------- 1 | 2 |
    3 | {{#each node.pages as |page|}} 4 |
  • 5 | 11 | {{page.title}} 12 | 13 |
  • 14 | {{/each}} 15 | 16 | {{#each node.children as |child|}} 17 |
  • 18 |
    19 | {{child.label}} 20 |
    21 |
      22 | {{#each child.pages as |page|}} 23 |
    • 24 | 29 | {{page.title}} 30 | 31 |
    • 32 | {{/each}} 33 |
    34 |
  • 35 | {{/each}} 36 |
37 |
38 | -------------------------------------------------------------------------------- /site/app/components/docs-viewer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import Component from '@ember/component'; 3 | import layout from 'ember-cli-addon-docs/components/docs-viewer/template'; 4 | import { inject as service } from '@ember/service'; 5 | 6 | export default Component.extend({ 7 | layout, 8 | docsRoutes: service(), 9 | router: service(), 10 | 11 | classNames: 'docs-viewer docs-flex docs-flex1', 12 | }); 13 | -------------------------------------------------------------------------------- /site/app/components/docs/quickstart-final.hbs: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{! BEGIN-SNIPPET quickstart-button-final-used }} 4 | 10 | Click me! 11 | 12 | {{! END-SNIPPET }} 13 |
14 | 17 | 24 |
25 |
26 | 29 | 36 |
37 |
38 | 42 | 46 | 50 | 54 |
-------------------------------------------------------------------------------- /site/app/components/docs/quickstart-final.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | import { tracked } from '@glimmer/tracking'; 4 | 5 | export default class extends Component { 6 | @tracked 7 | failRequest = false; 8 | 9 | @tracked 10 | disabled = false; 11 | 12 | // BEGIN-SNIPPET quickstart-button-final-used 13 | 14 | // ... 15 | 16 | doSomethingAsync = () => { 17 | const promise = new Promise((resolve, reject) => { 18 | const fn = this.failRequest ? reject : resolve; 19 | 20 | setTimeout(fn, 1000); 21 | }); 22 | 23 | return promise; 24 | }; 25 | 26 | @action 27 | onSuccess() { 28 | window.alert('Submit successful'); 29 | } 30 | 31 | @action 32 | onError() { 33 | window.alert('Submit failed'); 34 | } 35 | // ... 36 | // END-SNIPPET 37 | } 38 | -------------------------------------------------------------------------------- /site/app/components/docs/quickstart-guide.hbs: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{! BEGIN-SNIPPET quickstart-button-used }} 4 | 9 | Click me! 10 | 11 | {{! END-SNIPPET }} 12 |
13 | 16 | 23 |
24 |
25 | 29 | 33 | 37 | 41 | 45 |
-------------------------------------------------------------------------------- /site/app/components/docs/quickstart-guide.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | 4 | import { action } from '@ember/object'; 5 | 6 | export default class extends Component { 7 | // BEGIN-SNIPPET quickstart-button-used 8 | 9 | // ... 10 | @tracked 11 | failRequest = false; 12 | 13 | doSomethingAsync = () => { 14 | const promise = new Promise((resolve, reject) => { 15 | const fn = this.failRequest ? reject : resolve; 16 | 17 | setTimeout(fn, 1000); 18 | }); 19 | 20 | return promise; 21 | }; 22 | 23 | @action 24 | onSuccess() { 25 | window.alert('Submit successful'); 26 | } 27 | 28 | @action 29 | onError() { 30 | window.alert('Submit failed'); 31 | } 32 | // ... 33 | // END-SNIPPET 34 | } 35 | -------------------------------------------------------------------------------- /site/app/components/docs/typescript-usage.hbs: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{! BEGIN-SNIPPET typed-button-used }} 4 | 10 | .ts FTW 11 | 12 | {{! END-SNIPPET }} 13 |
14 | 17 | 24 |
25 |
26 | 29 | 36 |
37 |
38 | 39 | 40 | 41 | 42 |
-------------------------------------------------------------------------------- /site/app/components/docs/typescript-usage.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | import { tracked } from '@glimmer/tracking'; 4 | 5 | export default class extends Component { 6 | @tracked 7 | failRequest = false; 8 | 9 | @tracked 10 | disabled = false; 11 | 12 | // BEGIN-SNIPPET typescript-usage 13 | 14 | // ... 15 | 16 | doSomethingAsync = () => { 17 | const promise = new Promise((resolve, reject) => { 18 | const fn = this.failRequest ? reject : resolve; 19 | 20 | setTimeout(fn, 1000); 21 | }); 22 | 23 | return promise; 24 | }; 25 | 26 | @action 27 | onSuccess() { 28 | window.alert('Submit successful'); 29 | } 30 | 31 | @action 32 | onError() { 33 | window.alert('Submit failed'); 34 | } 35 | // ... 36 | // END-SNIPPET 37 | } 38 | -------------------------------------------------------------------------------- /site/app/components/mobile-docs-toc.hbs: -------------------------------------------------------------------------------- 1 |
2 | 23 | {{#if this.showToc}} 24 |
25 | 26 |
27 | {{/if}} 28 |
-------------------------------------------------------------------------------- /site/app/components/mobile-docs-toc.ts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { action } from '@ember/object'; 4 | 5 | export default class MobileDocsToc extends Component { 6 | @tracked showToc = false; 7 | 8 | @action toggleToc(): void { 9 | this.showToc = !this.showToc; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /site/app/components/page-headings.hbs: -------------------------------------------------------------------------------- 1 |
4 | 5 | {{#if page.headings.length}} 6 |
    7 |
  • 8 | 15 |
  • 16 | {{#each page.headings as |heading|}} 17 |
  • 18 | 29 | {{heading.title}} 30 | 31 | 32 | {{#if heading.headings.length}} 33 | 52 | {{/if}} 53 |
  • 54 | {{/each}} 55 |
56 | {{/if}} 57 |
58 |
59 | -------------------------------------------------------------------------------- /site/app/components/page-headings.ts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import scrollTo, { scrollToElement } from '../utils/scroll-to'; 3 | import { action } from '@ember/object'; 4 | 5 | interface PageHeadingsArgs {} //eslint-disable-line 6 | 7 | export default class PageHeadings extends Component { 8 | @action onClick(evt: MouseEvent): void { 9 | const href = (evt.target as HTMLElement).getAttribute('href'); 10 | if (href) { 11 | const toElement = document.querySelector(href) as HTMLElement; 12 | 13 | scrollToElement(toElement); 14 | } 15 | } 16 | 17 | @action scrollToTop(): void { 18 | scrollTo(0); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /site/app/components/quickstart-button-refined.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET quickstart-button-final }} 2 | 19 | {{! END-SNIPPET }} 20 | -------------------------------------------------------------------------------- /site/app/components/quickstart-button-refined.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET quickstart-button-final 2 | import Component from '@glimmer/component'; 3 | import { action } from '@ember/object'; 4 | import { useMachine } from 'ember-statecharts'; 5 | import quickstartButtonRefinedMachine from '../machines/quickstart-button-refined'; 6 | 7 | async function noop() {} 8 | 9 | export default class QuickstartButtonFinal extends Component { 10 | get onClick() { 11 | return this.args.onClick || noop; 12 | } 13 | 14 | get isBusy() { 15 | return this.statechart.state.matches({ activity: 'busy' }); 16 | } 17 | 18 | get isDisabled() { 19 | return this.statechart.state.matches({ interactivity: 'disabled' }); 20 | } 21 | 22 | get isInteractivityUnknown() { 23 | return this.statechart.state.matches({ interactivity: 'unknown' }); 24 | } 25 | get showAsDisabled() { 26 | const { isDisabled, isBusy, isInteractivityUnknown } = this; 27 | 28 | return isDisabled || isBusy || isInteractivityUnknown; 29 | } 30 | 31 | statechart = useMachine(this, () => { 32 | const { onSuccess, onError, onClick } = this; 33 | const { disabled } = this.args; 34 | 35 | return { 36 | machine: quickstartButtonRefinedMachine 37 | .withContext({ 38 | disabled, 39 | }) 40 | .withConfig({ 41 | actions: { 42 | handleSuccess: onSuccess, 43 | handleError: onError, 44 | }, 45 | services: { 46 | handleSubmit: onClick, 47 | }, 48 | guards: { 49 | isEnabled({ disabled }) { 50 | return !disabled; 51 | }, 52 | }, 53 | }), 54 | update: ({ send, machine: { context } }) => { 55 | const { disabled } = context; 56 | 57 | if (disabled) { 58 | send('DISABLE'); 59 | } else { 60 | send('ENABLE'); 61 | } 62 | }, 63 | }; 64 | }); 65 | 66 | @action 67 | handleClick() { 68 | this.statechart.send('SUBMIT'); 69 | } 70 | 71 | @action 72 | onSuccess(_context, { data: result }) { 73 | return this.args.onSuccess(result) || noop(); 74 | } 75 | 76 | @action 77 | onError(_context, { data: error }) { 78 | return this.args.onError(error) || noop(); 79 | } 80 | } 81 | // END-SNIPPET 82 | -------------------------------------------------------------------------------- /site/app/components/quickstart-button.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET quickstart-button }} 2 | 19 | {{! END-SNIPPET }} 20 | -------------------------------------------------------------------------------- /site/app/components/quickstart-button.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET quickstart-button 2 | import Component from '@glimmer/component'; 3 | import { action } from '@ember/object'; 4 | import { useMachine } from 'ember-statecharts'; 5 | 6 | import quickstartButtonMachine from '../machines/quickstart-button'; 7 | 8 | async function noop() {} 9 | 10 | export default class QuickstartButton extends Component { 11 | get onClick() { 12 | return this.args.onClick || noop; 13 | } 14 | 15 | statechart = useMachine(this, () => { 16 | const { onClick, onSuccess, onError } = this; 17 | 18 | return { 19 | machine: quickstartButtonMachine.withConfig({ 20 | actions: { 21 | handleSuccess: onSuccess, 22 | handleError: onError, 23 | }, 24 | services: { 25 | handleSubmit: onClick, 26 | }, 27 | }), 28 | }; 29 | }); 30 | 31 | get isBusy() { 32 | return this.statechart.state.matches('busy'); 33 | } 34 | 35 | get isDisabled() { 36 | return this.isBusy || this.args.disabled; 37 | } 38 | 39 | @action handleClick() { 40 | this.statechart.send('SUBMIT'); 41 | } 42 | 43 | @action onSuccess(_context, { data: result }) { 44 | return (this.args.onSuccess && this.args.onSuccess(result)) || noop(); 45 | } 46 | 47 | @action onError(_context, { data: error }) { 48 | return (this.args.onError && this.args.onError(error)) || noop(); 49 | } 50 | } 51 | // END-SNIPPET 52 | -------------------------------------------------------------------------------- /site/app/components/typed-button.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET typed-button }} 2 | 19 | {{! END-SNIPPET }} 20 | -------------------------------------------------------------------------------- /site/app/components/typed-button.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | // BEGIN-SNIPPET typed-button 3 | import Component from '@glimmer/component'; 4 | import { useMachine } from 'ember-statecharts'; 5 | import buttonMachine, { 6 | ButtonContext, 7 | ButtonEvent, 8 | ButtonState, 9 | } from '../machines/typed-button'; 10 | interface ButtonArgs { 11 | disabled?: boolean; 12 | onClick?: () => any; 13 | onSuccess?: (result: any) => any; 14 | onError?: (error: any) => any; 15 | } 16 | 17 | function noop() {} 18 | 19 | export default class TypedButton extends Component { 20 | get onClick(): any { 21 | return this.args.onClick || noop; 22 | } 23 | 24 | get isBusy() { 25 | return this.statechart.state.matches({ activity: 'busy' }); 26 | } 27 | 28 | get isDisabled() { 29 | return this.statechart.state.matches({ interactivity: 'disabled' }); 30 | } 31 | 32 | get isInteractivityUnknown() { 33 | return this.statechart.state.matches({ interactivity: 'unknown' }); 34 | } 35 | 36 | get showAsDisabled() { 37 | const { isDisabled, isBusy, isInteractivityUnknown } = this; 38 | 39 | return isDisabled || isBusy || isInteractivityUnknown; 40 | } 41 | 42 | statechart = useMachine( 43 | this, 44 | () => { 45 | return { 46 | machine: buttonMachine 47 | .withContext({ 48 | disabled: this.args.disabled, 49 | }) 50 | .withConfig({ 51 | actions: { 52 | handleSuccess: this.onSuccess, 53 | handleError: this.onError, 54 | }, 55 | services: { 56 | handleSubmit: this.onClick, 57 | }, 58 | }), 59 | update: ({ machine, send }) => { 60 | const disabled = machine.context?.disabled; 61 | 62 | if (disabled) { 63 | send('DISABLE'); 64 | } else { 65 | send('ENABLE'); 66 | } 67 | }, 68 | }; 69 | } 70 | ); 71 | 72 | handleClick = () => { 73 | this.statechart.send('SUBMIT'); 74 | }; 75 | 76 | onSuccess = ( 77 | _context: ButtonContext, 78 | { data: result }: Extract 79 | ): any => { 80 | const functionToCall = this.args.onSuccess || noop; 81 | 82 | return functionToCall(result); 83 | }; 84 | 85 | onError = ( 86 | _context: ButtonContext, 87 | { 88 | data: error, 89 | }: Extract 90 | ): any => { 91 | const functionToCall = this.args.onError || noop; 92 | 93 | return functionToCall(error); 94 | }; 95 | } 96 | // END-SNIPPET 97 | -------------------------------------------------------------------------------- /site/app/components/ui-button.hbs: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /site/app/components/ui-intro.md: -------------------------------------------------------------------------------- 1 | # Motivation 2 | 3 | `ember-statecharts` provides an easy way to use statecharts in ember applications. 4 | This is especially useful in `Ember.Component`-architecture but can be used across all layers of your application - e.g. when implementing global application state backed by an `Ember.Service` that needs to switch your application into a specifc mode based on data that your application receives via push events. 5 | 6 | By using statecharts you can improve your development workflow in multiple ways: 7 | 8 | * Model behavior explicitly. Instead of relying on implicit states that you manage by setting properties on your objects you will model your behavior as a set of explicit `state`s that handle events. 9 | * Create robust applications that won't break. Because behavior is only executed when a given state understands an event that is being triggered it is impossible to trigger invalid or unexpected application behavior. 10 | * Refactor and refine with confidence. Application flows modeled with statecharts are easy to change without the risk of breaking existing behavior. 11 | * Document behavior. Because statecharts can be visualized you finally have a way of communicating about application behavior with stakeholders that don't have a programming background. It will also help fellow developers to reason about behavior in your application. 12 | * Identify missing requirements. Because you make use of a visual language that describes behavior it gets very easy to identify holes in requirement documents. -------------------------------------------------------------------------------- /site/app/components/ui-loading.hbs: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /site/app/components/versions-dropdown.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#if this.isSuccess}} 3 | 15 | {{/if}} 16 |
17 | -------------------------------------------------------------------------------- /site/app/components/versions-dropdown.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | import { createMachine, assign } from 'xstate'; 4 | import { useMachine } from 'ember-statecharts'; 5 | import config from 'site/config/environment'; 6 | 7 | const versionsMachine = createMachine( 8 | { 9 | initial: 'loading', 10 | context: { 11 | versions: null, 12 | error: null, 13 | }, 14 | 15 | states: { 16 | loading: { 17 | invoke: { 18 | src: 'loadVersions', 19 | onDone: 'success', 20 | onError: 'error', 21 | }, 22 | }, 23 | success: { 24 | entry: [ 25 | assign({ 26 | versions: (context, { data }) => data, 27 | }), 28 | ], 29 | }, 30 | error: { 31 | entry: [ 32 | assign({ 33 | error: (context, { data }) => data, 34 | }), 35 | ], 36 | }, 37 | }, 38 | }, 39 | { 40 | services: { 41 | async loadVersions() {}, 42 | }, 43 | } 44 | ); 45 | export default class VersionsDropdown extends Component { 46 | statechart = useMachine(this, () => { 47 | const { loadVersions } = this; 48 | return { 49 | machine: versionsMachine.withConfig({ 50 | services: { 51 | loadVersions, 52 | }, 53 | }), 54 | }; 55 | }); 56 | 57 | get isSuccess() { 58 | return this.statechart.state.matches('success'); 59 | } 60 | 61 | get rootURL() { 62 | return config.rootURL; 63 | } 64 | 65 | get versions() { 66 | const versions = this.statechart.state.context.versions; 67 | const latest = versions['-latest']; 68 | const primary = versions['master']; 69 | 70 | const otherTags = Object.keys(versions) 71 | .filter((v) => !['-latest', 'master'].includes(v)) 72 | .map((key) => versions[key]) 73 | .sort((tagA, tagB) => { 74 | let keyA = tagA.tag.replace('v', ''); 75 | let keyB = tagB.tag.replace('v', ''); 76 | 77 | if (keyA > keyB) { 78 | return -1; 79 | } 80 | if (keyA < keyB) { 81 | return 1; 82 | } 83 | 84 | // names must be equal 85 | return 0; 86 | }); 87 | 88 | return [latest, primary, ...otherTags].map((v) => { 89 | if (v.path) { 90 | v.label = `${v.name} - ${v.sha.substring(0, 5)}`; 91 | } else { 92 | v.label = `${v.name} - ${v.tag}`; 93 | } 94 | 95 | return v; 96 | }); 97 | } 98 | 99 | @action redirectTo({ target: { value } }) { 100 | window.location.href = `/${value}`; 101 | } 102 | 103 | @action async loadVersions() { 104 | const fetchResult = await fetch('/versions.json'); 105 | 106 | if (fetchResult.ok) { 107 | return await fetchResult.json(); 108 | } 109 | 110 | throw Error('Error loading versions'); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /site/app/config/environment.d.ts: -------------------------------------------------------------------------------- 1 | export default config; 2 | 3 | /** 4 | * Type declarations for 5 | * import config from './config/environment' 6 | * 7 | * For now these need to be managed by the developer 8 | * since different ember addons can materialize new entries. 9 | */ 10 | declare const config: { 11 | environment: any; 12 | modulePrefix: string; 13 | podModulePrefix: string; 14 | locationType: string; 15 | rootURL: string; 16 | }; 17 | -------------------------------------------------------------------------------- /site/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/app/controllers/.gitkeep -------------------------------------------------------------------------------- /site/app/controllers/docs.ts: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { action } from '@ember/object'; 4 | 5 | export default class Docs extends Controller { 6 | @tracked currentHeadingId: string | undefined; 7 | 8 | @action 9 | setCurrentHeadingId(id: string | undefined): void { 10 | this.currentHeadingId = id; 11 | } 12 | } 13 | 14 | // DO NOT DELETE: this is how TypeScript knows how to look up your controllers. 15 | declare module '@ember/controller' { 16 | interface Registry { 17 | docs: Docs; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /site/app/controllers/docs/statecharts.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | import { tracked } from '@glimmer/tracking'; 4 | 5 | export default class StatechartsController extends Controller { 6 | @tracked 7 | counterCount = 0; 8 | @tracked 9 | count = 0; 10 | 11 | @action 12 | updateCount({ target: { value } }) { 13 | this.count = value; 14 | } 15 | 16 | @action 17 | syncCounterCount() { 18 | this.counterCount = +this.count; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /site/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/app/helpers/.gitkeep -------------------------------------------------------------------------------- /site/app/helpers/is-current-version.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import config from 'site/config/environment'; 3 | 4 | const { rootURL } = config; 5 | 6 | export default helper(function ([version]) { 7 | return `${version}/` === rootURL.replace('/', ''); 8 | }); 9 | -------------------------------------------------------------------------------- /site/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 14 | {{content-for "head-footer"}} 15 | 16 | 17 | {{content-for "body"}} 18 | 19 | 20 | 21 | 22 | {{content-for "body-footer"}} 23 | 24 | 25 | -------------------------------------------------------------------------------- /site/app/machines/counter-machine.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET counter-machine 2 | import { createMachine, assign } from 'xstate'; 3 | 4 | export default createMachine({ 5 | id: 'counterMachine', 6 | initial: 'inactive', 7 | context: { 8 | count: 0, 9 | }, 10 | on: { 11 | RESET_COUNT: { 12 | actions: [ 13 | assign({ 14 | count: (_context, { count }) => count, 15 | }), 16 | ], 17 | }, 18 | }, 19 | states: { 20 | inactive: { 21 | on: { 22 | ACTIVATE: 'active', 23 | }, 24 | }, 25 | active: { 26 | on: { 27 | DEACTIVATE: 'inactive', 28 | INCREMENT: { 29 | target: 'active', 30 | actions: [ 31 | assign({ 32 | count: (context) => context.count + 1, 33 | }), 34 | ], 35 | }, 36 | DECREMENT: { 37 | target: 'active', 38 | actions: [ 39 | assign({ 40 | count: (context) => context.count - 1, 41 | }), 42 | ], 43 | }, 44 | }, 45 | }, 46 | }, 47 | }); 48 | // END-SNIPPET 49 | -------------------------------------------------------------------------------- /site/app/machines/quickstart-button-refined.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET quickstart-button-machine-refined.js 2 | import { createMachine } from 'xstate'; 3 | 4 | export default createMachine( 5 | { 6 | type: 'parallel', 7 | states: { 8 | interactivity: { 9 | initial: 'unknown', 10 | states: { 11 | unknown: { 12 | on: { 13 | '': [ 14 | { target: 'enabled', cond: 'isEnabled' }, 15 | { target: 'disabled' }, 16 | ], 17 | }, 18 | }, 19 | enabled: { 20 | on: { 21 | DISABLE: 'disabled', 22 | }, 23 | }, 24 | disabled: { 25 | on: { 26 | ENABLE: 'enabled', 27 | }, 28 | }, 29 | }, 30 | }, 31 | activity: { 32 | initial: 'idle', 33 | states: { 34 | idle: { 35 | on: { 36 | SUBMIT: { 37 | target: 'busy', 38 | cond: 'isEnabled', 39 | }, 40 | }, 41 | }, 42 | busy: { 43 | entry: ['handleSubmit'], 44 | invoke: { 45 | src: 'handleSubmit', 46 | onDone: 'success', 47 | onError: 'error', 48 | }, 49 | }, 50 | success: { 51 | entry: ['handleSuccess'], 52 | on: { 53 | SUBMIT: { 54 | target: 'busy', 55 | cond: 'isEnabled', 56 | }, 57 | }, 58 | }, 59 | error: { 60 | entry: ['handleError'], 61 | on: { 62 | SUBMIT: { 63 | target: 'busy', 64 | cond: 'isEnabled', 65 | }, 66 | }, 67 | }, 68 | }, 69 | }, 70 | }, 71 | }, 72 | { 73 | actions: { 74 | handleSuccess() {}, 75 | handleError() {}, 76 | }, 77 | services: { 78 | handleSubmit: async () => {}, 79 | }, 80 | guards: { 81 | isEnabled(context) { 82 | return !context.disabled; 83 | }, 84 | }, 85 | } 86 | ); 87 | // END-SNIPPET 88 | -------------------------------------------------------------------------------- /site/app/machines/quickstart-button.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET quickstart-button-machine 2 | import { createMachine } from 'xstate'; 3 | 4 | export default createMachine( 5 | { 6 | initial: 'idle', 7 | states: { 8 | idle: { 9 | on: { 10 | SUBMIT: 'busy', 11 | }, 12 | }, 13 | busy: { 14 | invoke: { 15 | src: 'handleSubmit', 16 | onDone: 'success', 17 | onError: 'error', 18 | }, 19 | }, 20 | success: { 21 | entry: ['handleSuccess'], 22 | on: { 23 | SUBMIT: 'busy', 24 | }, 25 | }, 26 | error: { 27 | entry: ['handleError'], 28 | on: { 29 | SUBMIT: 'busy', 30 | }, 31 | }, 32 | }, 33 | }, 34 | { 35 | actions: { 36 | handleSuccess() {}, 37 | handleError() {}, 38 | }, 39 | services: { 40 | handleSubmit: async () => {}, 41 | }, 42 | } 43 | ); 44 | // END-SNIPPET 45 | -------------------------------------------------------------------------------- /site/app/machines/typed-button.ts: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET typed-button-machine 2 | import { createMachine } from 'xstate'; 3 | 4 | export interface ButtonContext { 5 | disabled?: boolean; 6 | } 7 | 8 | export type ButtonEvent = 9 | | { type: 'SUBMIT' } 10 | | { type: 'done.invoke.handleSubmit'; data: any } 11 | | { type: 'error.platform.handleSubmit'; data: any } 12 | | { type: 'ENABLE' } 13 | | { type: 'DISABLE' }; 14 | 15 | export type ButtonState = 16 | | { value: { activity: 'idle' }; context: { disabled?: boolean } } 17 | | { value: { activity: 'busy' }; context: { disabled?: boolean } } 18 | | { value: { activity: 'success' }; context: { disabled?: boolean } } 19 | | { value: { activity: 'error' }; context: { disabled?: boolean } } 20 | | { value: { interactivity: 'disabled' }; context: { disabled?: boolean } } 21 | | { value: { interactivity: 'unknown' }; context: { disabled?: boolean } } 22 | | { value: { interactivity: 'enabled' }; context: { disabled?: boolean } }; 23 | 24 | export default createMachine( 25 | { 26 | type: 'parallel', 27 | states: { 28 | interactivity: { 29 | initial: 'unknown', 30 | states: { 31 | unknown: { 32 | on: { 33 | '': [ 34 | { target: 'enabled', cond: 'isEnabled' }, 35 | { target: 'disabled' }, 36 | ], 37 | }, 38 | }, 39 | enabled: { 40 | on: { 41 | DISABLE: 'disabled', 42 | }, 43 | }, 44 | disabled: { 45 | on: { 46 | ENABLE: 'enabled', 47 | }, 48 | }, 49 | }, 50 | }, 51 | activity: { 52 | initial: 'idle', 53 | states: { 54 | idle: { 55 | on: { 56 | SUBMIT: { 57 | target: 'busy', 58 | cond: 'isEnabled', 59 | }, 60 | }, 61 | }, 62 | busy: { 63 | invoke: { 64 | src: 'handleSubmit', 65 | onDone: 'success', 66 | onError: 'error', 67 | }, 68 | }, 69 | success: { 70 | entry: ['handleSuccess'], 71 | on: { 72 | SUBMIT: { 73 | target: 'busy', 74 | cond: 'isEnabled', 75 | }, 76 | }, 77 | }, 78 | error: { 79 | entry: ['handleError'], 80 | on: { 81 | SUBMIT: { 82 | target: 'busy', 83 | cond: 'isEnabled', 84 | }, 85 | }, 86 | }, 87 | }, 88 | }, 89 | }, 90 | }, 91 | { 92 | actions: { 93 | handleSuccess() {}, 94 | handleError() {}, 95 | }, 96 | guards: { 97 | isEnabled(context) { 98 | return !context.disabled; 99 | }, 100 | }, 101 | services: { 102 | handleSubmit: async () => { 103 | return new Promise((resolve) => resolve); 104 | }, 105 | }, 106 | } 107 | ); 108 | // END-SNIPPET 109 | -------------------------------------------------------------------------------- /site/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/app/models/.gitkeep -------------------------------------------------------------------------------- /site/app/modifiers/intersect-headings.js: -------------------------------------------------------------------------------- 1 | import Modifier from 'ember-modifier'; 2 | import { action } from '@ember/object'; 3 | 4 | function getHeadingIds(headings, output = []) { 5 | if (typeof headings === 'undefined') { 6 | return []; 7 | } 8 | headings.forEach((heading) => { 9 | output.push(heading.id); 10 | getHeadingIds(heading.headings, output); 11 | }); 12 | return output; 13 | } 14 | 15 | export default class IntersectHeadingsModifier extends Modifier { 16 | handler = null; 17 | headings = []; 18 | observer = null; 19 | activeIndex = null; 20 | 21 | @action 22 | handleObserver(elements) { 23 | // Based on https://taylor.callsen.me/modern-navigation-menus-with-css-position-sticky-and-intersectionobservers/ 24 | 25 | // current index must be memoized or tracked outside of function for comparison 26 | let localActiveIndex = this.activeIndex; 27 | 28 | // track which elements register above or below the document's current position 29 | const aboveIndeces = []; 30 | const belowIndeces = []; 31 | 32 | // loop through each intersection element 33 | // due to the asychronous nature of observers, callbacks must be designed to handle 1 or many intersecting elements 34 | elements.forEach((element) => { 35 | // detect if intersecting element is above the browser viewport; include cross browser logic 36 | const boundingClientRectY = 37 | typeof element.boundingClientRect.y !== 'undefined' 38 | ? element.boundingClientRect.y 39 | : element.boundingClientRect.top; 40 | const rootBoundsY = 41 | typeof element.rootBounds.y !== 'undefined' 42 | ? element.rootBounds.y 43 | : element.rootBounds.top; 44 | const isAbove = boundingClientRectY < rootBoundsY; 45 | 46 | const id = element.target.getAttribute('id'); 47 | const intersectingElemIdx = this.headings.findIndex((item) => item == id); 48 | 49 | // record index as either above or below current index 50 | if (isAbove) aboveIndeces.push(intersectingElemIdx); 51 | else belowIndeces.push(intersectingElemIdx); 52 | }); 53 | 54 | // determine min and max fired indeces values (support for multiple elements firing at once) 55 | const minIndex = Math.min(...belowIndeces); 56 | const maxIndex = Math.max(...aboveIndeces); 57 | 58 | // determine how to adjust localActiveIndex based on scroll direction 59 | if (aboveIndeces.length > 0) { 60 | // scrolling down - set to max of fired indeces 61 | localActiveIndex = maxIndex; 62 | } else if (belowIndeces.length > 0 && minIndex <= this.activeIndex) { 63 | // scrolling up - set to minimum of fired indeces 64 | localActiveIndex = minIndex - 1 >= 0 ? minIndex - 1 : 0; 65 | } 66 | 67 | // render new index to DOM (if required) 68 | if (localActiveIndex != this.activeIndex) { 69 | this.activeIndex = localActiveIndex; 70 | 71 | this.handler(this.headings[this.activeIndex]); 72 | } 73 | } 74 | 75 | observe() { 76 | if ('IntersectionObserver' in window) { 77 | this.observer = new IntersectionObserver(this.handleObserver, { 78 | rootMargin: '-96px', // Distance from top to heading id 79 | threshold: 1.0, 80 | }); 81 | 82 | this.headings.forEach((id) => { 83 | const el = document.getElementById(id); 84 | if (el) { 85 | this.observer.observe(el); 86 | } 87 | }); 88 | } 89 | } 90 | 91 | unobserve() { 92 | if (this.observer) { 93 | this.observer.disconnect(); 94 | } 95 | } 96 | 97 | didUpdateArguments() { 98 | this.unobserve(); 99 | } 100 | 101 | didReceiveArguments() { 102 | const [handler] = this.args.positional; 103 | this.handler = handler; 104 | this.headings = getHeadingIds(this.args.named.headings); 105 | 106 | this.observe(); 107 | } 108 | 109 | willRemove() { 110 | this.unobserve(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /site/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'site/config/environment'; 3 | import { addDocfyRoutes } from '@docfy/ember'; 4 | 5 | export default class Router extends EmberRouter { 6 | location = config.locationType; 7 | rootURL = config.rootURL; 8 | } 9 | 10 | Router.map(function () { 11 | addDocfyRoutes(this); 12 | }); 13 | -------------------------------------------------------------------------------- /site/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/app/routes/.gitkeep -------------------------------------------------------------------------------- /site/app/routes/application.ts: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import config from 'site/config/environment'; 3 | import { action } from '@ember/object'; 4 | 5 | export default class ApplicationRoute extends Route { 6 | @action didTransition(): void { 7 | if ( 8 | config.environment !== 'test' && 9 | window && 10 | typeof window.scrollTo === 'function' 11 | ) { 12 | window.scrollTo(0, 0); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /site/app/snippets/quickstart/onEntry.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default // BEGIN-SNIPPET quickstart-on-entry 3 | ({ 4 | initial: 'idle', 5 | states: { 6 | idle: { 7 | on: { 8 | SUBMIT: 'busy', 9 | }, 10 | }, 11 | busy: { 12 | entry: ['handleSubmit'], 13 | }, 14 | }, 15 | }, 16 | { 17 | actions: { 18 | handleSubmit(/*context, event*/) {}, 19 | }, 20 | }); 21 | // END-SNIPPET 22 | -------------------------------------------------------------------------------- /site/app/snippets/quickstart/onExit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default // BEGIN-SNIPPET quickstart-on-exit 3 | ({ 4 | initial: 'idle', 5 | states: { 6 | idle: { 7 | on: { 8 | SUBMIT: 'busy', 9 | }, 10 | exit: ['handleSubmit'], 11 | }, 12 | busy: {}, 13 | }, 14 | }, 15 | { 16 | actions: { 17 | handleSubmit(/*context, event*/) {}, 18 | }, 19 | }); 20 | // END-SNIPPET 21 | -------------------------------------------------------------------------------- /site/app/snippets/quickstart/transition.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default // BEGIN-SNIPPET quickstart-transition 3 | ({ 4 | initial: 'idle', 5 | states: { 6 | idle: { 7 | on: { 8 | SUBMIT: { 9 | target: 'busy', 10 | actions: ['handleSubmit'], 11 | }, 12 | }, 13 | }, 14 | busy: {}, 15 | }, 16 | }, 17 | { 18 | actions: { 19 | handleSubmit(/*context, event*/) {}, 20 | }, 21 | }); 22 | // END-SNIPPET 23 | -------------------------------------------------------------------------------- /site/app/styles/app.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss/base"; 2 | @import "tailwindcss/components"; 3 | @import "tailwindcss/utilities"; 4 | 5 | .w-128 { 6 | width: 32rem; 7 | } 8 | 9 | .h-128 { 10 | height: 32rem; 11 | } 12 | 13 | .lds-ring { 14 | display: inline-block; 15 | position: relative; 16 | width: 16px; 17 | height: 16px; 18 | } 19 | 20 | .lds-ring div { 21 | box-sizing: border-box; 22 | display: block; 23 | position: absolute; 24 | width: 16px; 25 | height: 16px; 26 | border: 2px solid #fff; 27 | border-radius: 50%; 28 | animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 29 | border-color: #fff transparent transparent transparent; 30 | } 31 | .lds-ring div:nth-child(1) { 32 | animation-delay: -0.45s; 33 | } 34 | .lds-ring div:nth-child(2) { 35 | animation-delay: -0.3s; 36 | } 37 | .lds-ring div:nth-child(3) { 38 | animation-delay: -0.15s; 39 | } 40 | @keyframes lds-ring { 41 | 0% { 42 | transform: rotate(0deg); 43 | } 44 | 100% { 45 | transform: rotate(360deg); 46 | } 47 | } 48 | 49 | .quote { 50 | @apply flex; 51 | } 52 | .quote:before { 53 | content: ""; 54 | } 55 | 56 | .quote:after { 57 | content: ""; 58 | } 59 | 60 | .fancy-underline { 61 | @apply inline-block relative 62 | 63 | } 64 | .fancy-underline::before { 65 | content: ''; 66 | left: 50%; 67 | @apply bg-current absolute bottom-0 w-0 h-0.5 transform transition-all ease-in-out 68 | } 69 | 70 | .fancy-underline:hover::before { 71 | @apply w-full left-0 72 | } 73 | 74 | .text-underline-under { 75 | text-underline-position: under; 76 | } 77 | 78 | /* for easier styling of demo blocks - prism and prose will be overidden */ 79 | pre[class*="language-"] { 80 | margin: 0; 81 | } 82 | -------------------------------------------------------------------------------- /site/app/templates/docs.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
9 | 10 | 11 |
14 | {{outlet}} 15 |
16 |
17 |
20 | 21 |
22 | {{#if previous}} 23 | 28 | 33 | 34 | 35 | 36 | {{previous.title}} 37 | 38 | {{/if}} 39 |
40 |
41 | {{#if next}} 42 | 43 | {{next.title}} 44 | 45 | 46 | 51 | 56 | 57 | {{/if}} 58 |
59 |
60 |
61 |
62 | 65 |
66 |
-------------------------------------------------------------------------------- /site/app/utils/scroll-to.ts: -------------------------------------------------------------------------------- 1 | // http://goo.gl/5HLl8 2 | const easeInOutQuad = (t: number, b: number, c: number, d: number): number => { 3 | t /= d / 2; 4 | if (t < 1) { 5 | return (c / 2) * t * t + b; 6 | } 7 | t--; 8 | return (-c / 2) * (t * (t - 2) - 1) + b; 9 | }; 10 | 11 | function scrollTo( 12 | toPosition: number, 13 | callback?: () => void, 14 | duration = 500 15 | ): void { 16 | const scrollingElement = document.scrollingElement 17 | ? document.scrollingElement 18 | : document.body; 19 | const startPosition = scrollingElement.scrollTop; 20 | const change = toPosition - startPosition; 21 | let currentTime = 0; 22 | const increment = 20; 23 | 24 | const animateScroll = (): void => { 25 | currentTime += increment; 26 | scrollingElement.scrollTop = easeInOutQuad( 27 | currentTime, 28 | startPosition, 29 | change, 30 | duration 31 | ); 32 | 33 | if (currentTime < duration) { 34 | requestAnimationFrame(animateScroll); 35 | } else { 36 | if (callback && typeof callback === 'function') { 37 | callback(); 38 | } 39 | } 40 | }; 41 | animateScroll(); 42 | } 43 | 44 | function scrollToElement( 45 | element: HTMLElement, 46 | callback?: () => void, 47 | duration = 500 48 | ): void { 49 | const toPosition = element.offsetTop; 50 | scrollTo(toPosition, callback, duration); 51 | } 52 | 53 | export { scrollToElement }; 54 | export default scrollTo; 55 | -------------------------------------------------------------------------------- /site/config/deploy.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = function (/* deployTarget */) { 5 | let ENV = { 6 | build: {}, 7 | git: { 8 | commitMessage: 'chore: deploy %@', 9 | }, 10 | }; 11 | return ENV; 12 | }; 13 | -------------------------------------------------------------------------------- /site/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "3.28.6", 7 | "blueprints": [ 8 | { 9 | "name": "addon", 10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output", 11 | "codemodsSource": "ember-addon-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--yarn", 15 | "--no-welcome" 16 | ] 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /site/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const deployTarget = process.env.DEPLOY_TARGET || 'development'; 5 | 6 | let ENV = { 7 | modulePrefix: 'site', 8 | deployTarget, 9 | environment, 10 | rootURL: '/', 11 | locationType: 'auto', 12 | EmberENV: { 13 | FEATURES: { 14 | // Here you can enable experimental features on an ember canary build 15 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 16 | }, 17 | EXTEND_PROTOTYPES: { 18 | // Prevent Ember Data from overriding Date.parse. 19 | Date: false, 20 | }, 21 | }, 22 | 23 | APP: { 24 | // Here you can pass flags/options to your application instance 25 | // when it is created 26 | }, 27 | plausible: { 28 | src: 'https://analytics.effective-ember.com/js/plausible.js', 29 | domain: null, 30 | }, 31 | }; 32 | 33 | if (process.env.DEPLOY_TARGET) { 34 | const versionedDocInfo = require('../lib/versioned-doc-info'); 35 | ENV.rootURL = versionedDocInfo(process.env.DEPLOY_TARGET).rootURL; 36 | } 37 | 38 | if (environment === 'development') { 39 | // ENV.APP.LOG_RESOLVER = true; 40 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 41 | // ENV.APP.LOG_TRANSITIONS = true; 42 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 43 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 44 | } 45 | 46 | if (environment === 'test') { 47 | // Testem prefers this... 48 | ENV.locationType = 'none'; 49 | 50 | // keep test console output quieter 51 | ENV.APP.LOG_ACTIVE_GENERATION = false; 52 | ENV.APP.LOG_VIEW_LOOKUPS = false; 53 | 54 | ENV.APP.rootElement = '#ember-testing'; 55 | ENV.APP.autoboot = false; 56 | } 57 | 58 | if (environment === 'production') { 59 | // here you can enable a production-specific feature 60 | ENV.plausible.domain = 'ember-statecharts.com'; 61 | } 62 | 63 | return ENV; 64 | }; 65 | -------------------------------------------------------------------------------- /site/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /site/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | // Ember's browser support policy is changing, and IE11 support will end in 10 | // v4.0 onwards. 11 | // 12 | // See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy 13 | // 14 | // If you need IE11 support on a version of Ember that still offers support 15 | // for it, uncomment the code block below. 16 | // 17 | // const isCI = Boolean(process.env.CI); 18 | // const isProduction = process.env.EMBER_ENV === 'production'; 19 | // 20 | // if (isCI || isProduction) { 21 | // browsers.push('ie 11'); 22 | // } 23 | 24 | module.exports = { 25 | browsers, 26 | }; 27 | -------------------------------------------------------------------------------- /site/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const crawl = require('prember-crawler'); 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function (defaults) { 6 | let app = new EmberApp(defaults, { 7 | // Add options here 8 | postcssOptions: { 9 | compile: { 10 | plugins: [ 11 | require('postcss-import'), 12 | require('tailwindcss')('./tailwind.config.js'), 13 | require('autoprefixer'), 14 | ], 15 | }, 16 | }, 17 | snippetPaths: ['app'], 18 | snippetSearchPaths: ['app'], //, '../../docs'], 19 | 'ember-prism': { 20 | theme: 'tomorrow', 21 | components: ['markup-templating', 'handlebars', 'typescript'], 22 | }, 23 | prember: { 24 | urls: async ({ visit }) => { 25 | let docsURLs = await crawl({ 26 | visit, 27 | startingFrom: ['/docs'], 28 | selector: 'a[data-prember]', 29 | }); 30 | 31 | let otherURLS = ['/', '/docs']; 32 | 33 | return docsURLs.concat(otherURLS); 34 | }, 35 | }, 36 | }); 37 | 38 | /* 39 | This build file specifies the options for the dummy test app of this 40 | addon, located in `/tests/dummy` 41 | This build file does *not* influence how the addon or the app using it 42 | behave. You most likely want to be modifying `./index.js` or app's build file 43 | */ 44 | 45 | const { maybeEmbroider } = require('@embroider/test-setup'); 46 | return maybeEmbroider(app, { 47 | skipBabel: [ 48 | { 49 | package: 'qunit', 50 | }, 51 | ], 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /site/lib/deploy-versioned-doc/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | const versionedDocInfo = require('../versioned-doc-info'); 5 | const path = require('path'); 6 | const fs = require('fs-extra'); 7 | const os = require('os'); 8 | 9 | module.exports = { 10 | name: require('./package').name, 11 | 12 | createDeployPlugin: function (options) { 13 | return { 14 | name: options.name, 15 | 16 | async willUpload(context) { 17 | const { 18 | deployTarget, 19 | distDir, 20 | gitDeploy: { worktreePath }, 21 | } = context; 22 | 23 | const { 24 | name, 25 | destDir, 26 | sha, 27 | tag, 28 | path: _path, 29 | } = versionedDocInfo(deployTarget); 30 | 31 | // cache current build 32 | let currentBuild = path.join(os.tmpdir(), 'current-build-'); 33 | fs.moveSync(distDir, currentBuild); 34 | 35 | // ensure we have a ${worktree}/versions/ 36 | let versions = path.resolve(worktreePath, 'versions'); 37 | fs.ensureDirSync(versions); 38 | 39 | if (deployTarget === 'latest') { 40 | // keep the current versions 41 | fs.copySync(versions, path.resolve(currentBuild, 'versions')); 42 | 43 | // make current build the new dist 44 | fs.moveSync(currentBuild, distDir); 45 | } else { 46 | // keep current state of gh-pages 47 | fs.copySync(worktreePath, distDir); 48 | 49 | // move current build into versions/${branch || tag} 50 | fs.moveSync(currentBuild, path.resolve(distDir, destDir), { 51 | overwrite: true, 52 | }); 53 | } 54 | 55 | // get ${worktree}/versions.json 56 | let versionsJSON = {}; 57 | let versionsFile = path.resolve(worktreePath, 'versions.json'); 58 | if (fs.existsSync(versionsFile)) { 59 | versionsJSON = fs.readJsonSync(versionsFile); 60 | } 61 | 62 | let key = name; 63 | 64 | // update key for latest, this is for ember-cli-addon-docs compatibility 65 | if (deployTarget === 'latest') { 66 | key = '-latest'; 67 | } 68 | 69 | versionsJSON[key] = { 70 | path: _path, 71 | name, 72 | sha, 73 | tag, 74 | }; 75 | 76 | // write current versions.json 77 | fs.writeFileSync( 78 | path.resolve(distDir, 'versions.json'), 79 | JSON.stringify(versionsJSON, null, 2) 80 | ); 81 | 82 | fs.removeSync(currentBuild); 83 | }, 84 | }; 85 | }, 86 | }; 87 | -------------------------------------------------------------------------------- /site/lib/deploy-versioned-doc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deploy-versioned-doc", 3 | "keywords": [ 4 | "ember-addon", 5 | "ember-cli-deploy-plugin" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /site/lib/plausible/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'plausible', 6 | 7 | contentFor(type, config) { 8 | const plausible = config.plausible || {}; 9 | 10 | if (type === 'head-footer' && plausible.src && plausible.domain) { 11 | return ``; 12 | } 13 | 14 | return ''; 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /site/lib/plausible/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plausible", 3 | "keywords": [ 4 | "ember-addon" 5 | ], 6 | "dependencies": { 7 | "ember-cli-babel": "*", 8 | "ember-cli-typescript": "*" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /site/lib/versioned-doc-info.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | const getRepoInfo = require('git-repo-info'); 5 | 6 | module.exports = function (deployTarget) { 7 | let dir, name, rootURL, _path; 8 | let { sha, tag, branch } = getRepoInfo(); 9 | 10 | if (deployTarget === 'tag') { 11 | dir = tag; 12 | name = tag; 13 | branch = null; 14 | } else if (deployTarget === 'branch') { 15 | dir = branch; 16 | name = branch; 17 | tag = null; 18 | } else if (deployTarget === 'latest') { 19 | dir = 'latest'; 20 | name = 'Latest'; 21 | } else { 22 | throw new Error(`unsupported deploy target ${deployTarget}`); 23 | } 24 | 25 | let destDir = `versions/${dir}`; 26 | 27 | if (deployTarget === 'latest') { 28 | rootURL = '/'; 29 | _path = ''; 30 | } else { 31 | rootURL = `/${destDir}/`; 32 | _path = destDir; 33 | } 34 | 35 | return { 36 | name, 37 | sha, 38 | tag, 39 | branch, 40 | destDir, 41 | rootURL, 42 | deployTarget, 43 | path: _path, 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "site", 4 | "version": "0.13.6", 5 | "description": "Site for ember-statecharts", 6 | "homepage": "https://ember-statecharts.com/", 7 | "repository": "https://github.com/LevelbossMike/ember-statecharts", 8 | "license": "MIT", 9 | "author": "", 10 | "types": "./index.d.ts", 11 | "scripts": { 12 | "build": "ember build --environment=production", 13 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", 14 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", 15 | "lint:hbs": "ember-template-lint .", 16 | "lint:hbs:fix": "ember-template-lint . --fix", 17 | "lint:js": "eslint . --cache", 18 | "lint:js:fix": "eslint . --fix", 19 | "prepublishOnly": "ember ts:precompile", 20 | "postpublish": "ember ts:clean", 21 | "start": "ember serve", 22 | "test": "npm-run-all lint test:*", 23 | "test:ember": "ember test --test-port=0" 24 | }, 25 | "dependencies": { 26 | "@glimmer/tracking": "^1.0.4", 27 | "ember-auto-import": "^2.4.3", 28 | "ember-cli-babel": "^7.26.10", 29 | "ember-cli-htmlbars": "^5.7.2", 30 | "ember-cli-typescript": "^3.1.4", 31 | "ember-statecharts": "workspace:*" 32 | }, 33 | "devDependencies": { 34 | "@docfy/ember": "^0.5.0", 35 | "@docfy/plugin-with-prose": "^0.5.0", 36 | "@ember/optional-features": "^2.0.0", 37 | "@ember/render-modifiers": "^1.0.2", 38 | "@ember/test-helpers": "^2.6.0", 39 | "@embroider/test-setup": "^0.48.1", 40 | "@glimmer/component": "^1.0.4", 41 | "@mapbox/rehype-prism": "^0.6.0", 42 | "@release-it/conventional-changelog": "^1.1.4", 43 | "@tailwindcss/aspect-ratio": "^0.2.0", 44 | "@tailwindcss/typography": "^0.4.0", 45 | "@types/ember": "^3.16.5", 46 | "@types/ember-qunit": "^3.4.9", 47 | "@types/ember__test-helpers": "^1.7.2", 48 | "@types/qunit": "^2.9.1", 49 | "@types/rsvp": "^4.0.3", 50 | "@typescript-eslint/eslint-plugin": "^4.0.1", 51 | "@typescript-eslint/parser": "^4.0.1", 52 | "autoprefixer": "^10.2.5", 53 | "babel-eslint": "^10.1.0", 54 | "broccoli-asset-rev": "^3.0.0", 55 | "ember-cli": "~3.28.6", 56 | "ember-cli-dependency-checker": "^3.2.0", 57 | "ember-cli-deploy": "^1.0.2", 58 | "ember-cli-deploy-build": "^2.0.0", 59 | "ember-cli-deploy-git": "^1.3.4", 60 | "ember-cli-fastboot": "^2.2.3", 61 | "ember-cli-inject-live-reload": "^2.1.0", 62 | "ember-cli-postcss": "^7.0.0", 63 | "ember-cli-sri": "^2.1.1", 64 | "ember-cli-terser": "^4.0.2", 65 | "ember-cli-typescript-blueprints": "^3.0.0", 66 | "ember-disable-prototype-extensions": "^1.1.3", 67 | "ember-export-application-global": "^2.0.1", 68 | "ember-load-initializers": "^2.1.2", 69 | "ember-modifier": "^3.2.7", 70 | "ember-page-title": "^6.2.2", 71 | "ember-qunit": "^5.1.5", 72 | "ember-resolver": "^8.0.3", 73 | "ember-showcase": "0.2.0", 74 | "ember-source": "~3.28.8", 75 | "ember-source-channel-url": "^3.0.0", 76 | "ember-svg-jar": "^2.3.3", 77 | "ember-template-lint": "^3.15.0", 78 | "ember-try": "^1.4.0", 79 | "eslint": "^7.32.0", 80 | "eslint-config-prettier": "^8.3.0", 81 | "eslint-plugin-ember": "^10.5.8", 82 | "eslint-plugin-node": "^11.1.0", 83 | "eslint-plugin-prettier": "^3.4.1", 84 | "eslint-plugin-qunit": "^6.2.0", 85 | "fs-extra": "^9.1.0", 86 | "git-repo-info": "^2.1.1", 87 | "loader.js": "^4.7.0", 88 | "npm-run-all": "^4.1.5", 89 | "postcss-import": "^14.0.2", 90 | "prember": "^1.0.5", 91 | "prember-crawler": "^1.0.0", 92 | "prettier": "^2.5.1", 93 | "qunit": "^2.17.2", 94 | "qunit-dom": "^1.6.0", 95 | "release-it": "^13.6.2", 96 | "remark-autolink-headings": "^6.0.1", 97 | "tailwindcss": "^2.1.2", 98 | "typedoc": "^0.20.36", 99 | "typescript": "^4.0.2", 100 | "webpack": "^5.74.0", 101 | "xstate": "^4.12.0" 102 | }, 103 | "engines": { 104 | "node": "12.* || 14.* || >= 16" 105 | }, 106 | "ember": { 107 | "edition": "octane" 108 | }, 109 | "ember-addon": { 110 | "paths": [ 111 | "lib/deploy-versioned-doc", 112 | "lib/plausible" 113 | ] 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /site/public/CNAME: -------------------------------------------------------------------------------- 1 | ember-statecharts.com 2 | -------------------------------------------------------------------------------- /site/public/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /site/public/effective-ember.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /site/public/ember-statecharts-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Ember 33 | Statecharts 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /site/public/ember-statecharts-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Ember 33 | Statecharts 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /site/public/harel-paper-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/public/harel-paper-1.png -------------------------------------------------------------------------------- /site/public/harel-statechart-paper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/public/harel-statechart-paper.png -------------------------------------------------------------------------------- /site/public/quote.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /site/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /site/public/statecharts-blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/public/statecharts-blog.png -------------------------------------------------------------------------------- /site/public/statecharts.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /site/public/tutorial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/public/tutorial.png -------------------------------------------------------------------------------- /site/public/xstate-guides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/public/xstate-guides.png -------------------------------------------------------------------------------- /site/public/xstate-viz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/public/xstate-viz.png -------------------------------------------------------------------------------- /site/public/xstate.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /site/public/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /site/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: { 6 | colors: { 7 | ember: '#e04e39', 8 | }, 9 | }, 10 | }, 11 | variants: { 12 | extend: {}, 13 | }, 14 | plugins: [ 15 | require('@tailwindcss/typography'), 16 | require('@tailwindcss/aspect-ratio'), 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /site/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /site/tests/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/tests/helpers/.gitkeep -------------------------------------------------------------------------------- /site/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} 17 | {{content-for "test-head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | {{content-for "test-body"}} 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{content-for "body-footer"}} 37 | {{content-for "test-body-footer"}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /site/tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/tests/integration/.gitkeep -------------------------------------------------------------------------------- /site/tests/integration/components/x-button-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render, click, waitFor } from '@ember/test-helpers'; 4 | import hbs from 'htmlbars-inline-precompile'; 5 | import { Promise, resolve, reject } from 'rsvp'; 6 | 7 | module('Integration | Component | quickstart-button', function (hooks) { 8 | setupRenderingTest(hooks); 9 | 10 | test('it renders a button', async function (assert) { 11 | await render(hbs``); 12 | 13 | assert.dom('button').exists('button is rendered'); 14 | }); 15 | 16 | test('it is possible to use the button in block-format', async function (assert) { 17 | let TEXT = 'Hello'; 18 | 19 | this.set('text', TEXT); 20 | 21 | await render(hbs` 22 | 23 | {{this.text}} World! 24 | 25 | `); 26 | 27 | assert.dom('[data-test-button]').hasText('Hello World!'); 28 | }); 29 | 30 | test("it's possible to pass an onClick-action handler to the button", async function (assert) { 31 | assert.expect(1); 32 | 33 | this.set('wat', function () { 34 | assert.ok(true, 'action handler was called'); 35 | }); 36 | 37 | await render(hbs` 38 | 42 | `); 43 | 44 | await click('[data-test-button]'); 45 | }); 46 | 47 | test("when the action triggered by the button clicked gets fired and takes time it's not possible to trigger it again", async function (assert) { 48 | assert.expect(3); 49 | 50 | this.set('onClick', function () { 51 | assert.ok(true, 'onClick was triggered'); 52 | return new Promise(function () {}); 53 | }); 54 | 55 | await render(hbs` 56 | 57 | `); 58 | 59 | click('[data-test-button]'); 60 | 61 | await waitFor('[data-test-loading]'); 62 | 63 | assert 64 | .dom('[data-test-loading]') 65 | .exists('button is displayed with loading ui while busy'); 66 | 67 | this.set('onClick', function () { 68 | assert.ok(false, 'onClick should not be triggered again'); 69 | }); 70 | 71 | assert.dom('[data-test-button]').isDisabled('button is disabled when busy'); 72 | }); 73 | 74 | test("when passing the disabled property the button is disabled and won't trigger its action", async function (assert) { 75 | assert.expect(1); 76 | 77 | this.set('onClick', function () { 78 | assert.ok(false, 'onClick should not be triggered'); 79 | }); 80 | 81 | await render(hbs` 82 | 88 | `); 89 | 90 | assert.dom('[data-test-button]').isDisabled('button is disabled'); 91 | }); 92 | 93 | test('when the triggered action resolves the `onSuccess` handler is triggered', async function (assert) { 94 | assert.expect(1); 95 | 96 | this.set('onClick', function () { 97 | return resolve(); 98 | }); 99 | 100 | this.set('onSuccess', function () { 101 | assert.ok(true, '`onSuccess` was triggered'); 102 | }); 103 | 104 | await render(hbs` 105 | 111 | `); 112 | 113 | await click('[data-test-button]'); 114 | }); 115 | 116 | test('when the triggered action rejects the `onError` handler is triggered', async function (assert) { 117 | assert.expect(1); 118 | 119 | this.set('onClick', function () { 120 | return reject(); 121 | }); 122 | 123 | this.set('onError', function () { 124 | assert.ok(true, '`onError` was triggered'); 125 | }); 126 | 127 | await render(hbs` 128 | 134 | `); 135 | 136 | await click('[data-test-button]'); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /site/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'site/app'; 2 | import config from 'site/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setApplication(Application.create(config.APP)); 9 | 10 | setup(QUnit.assert); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /site/tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/site/tests/unit/.gitkeep -------------------------------------------------------------------------------- /site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "allowJs": true, 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "noImplicitAny": true, 8 | "noImplicitThis": true, 9 | "alwaysStrict": true, 10 | "strictNullChecks": true, 11 | "strictPropertyInitialization": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noEmitOnError": false, 17 | "noEmit": true, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "baseUrl": ".", 21 | "module": "es6", 22 | "experimentalDecorators": true, 23 | }, 24 | "include": [ 25 | "app/**/*", 26 | "types/**/*" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false, 9 | 10 | /** 11 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript 12 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available. 13 | */ 14 | "isTypeScriptProject": false 15 | } 16 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | ecmaVersion: 2018, 8 | sourceType: 'module', 9 | ecmaFeatures: { 10 | legacyDecorators: true, 11 | }, 12 | }, 13 | plugins: ['ember'], 14 | extends: [ 15 | 'eslint:recommended', 16 | 'plugin:ember/recommended', 17 | 'plugin:prettier/recommended', 18 | ], 19 | env: { 20 | browser: true, 21 | }, 22 | rules: {}, 23 | overrides: [ 24 | // node files 25 | { 26 | files: [ 27 | './.eslintrc.js', 28 | './.prettierrc.js', 29 | './.template-lintrc.js', 30 | './ember-cli-build.js', 31 | './testem.js', 32 | './blueprints/*/index.js', 33 | './config/**/*.js', 34 | './lib/*/index.js', 35 | './server/**/*.js', 36 | ], 37 | parserOptions: { 38 | sourceType: 'script', 39 | }, 40 | env: { 41 | browser: false, 42 | node: true, 43 | }, 44 | plugins: ['node'], 45 | extends: ['plugin:node/recommended'], 46 | rules: { 47 | // this can be removed once the following is fixed 48 | // https://github.com/mysticatea/eslint-plugin-node/issues/77 49 | 'node/no-unpublished-require': 'off', 50 | }, 51 | }, 52 | { 53 | // test files 54 | files: ['tests/**/*-test.{js,ts}'], 55 | extends: ['plugin:qunit/recommended'], 56 | }, 57 | ], 58 | }; 59 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /.eslintcache 16 | /connect.lock 17 | /coverage/ 18 | /libpeerconnection.log 19 | /npm-debug.log* 20 | /testem.log 21 | /yarn-error.log 22 | 23 | # ember-try 24 | /.node_modules.ember-try/ 25 | /bower.json.ember-try 26 | /npm-shrinkwrap.json.ember-try 27 | /package.json.ember-try 28 | /package-lock.json.ember-try 29 | /yarn.lock.ember-try 30 | 31 | # broccoli-debug 32 | /DEBUG/ 33 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | .lint-todo/ 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | }; 6 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/README.md: -------------------------------------------------------------------------------- 1 | # test-app-ts 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | * [Git](https://git-scm.com/) 11 | * [Node.js](https://nodejs.org/) (with npm) 12 | * [Ember CLI](https://cli.emberjs.com/release/) 13 | * [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | * `git clone ` this repository 18 | * `cd test-app-ts` 19 | * `npm install` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | * `ember test` 34 | * `ember test --server` 35 | 36 | ### Linting 37 | 38 | * `npm run lint` 39 | * `npm run lint:fix` 40 | 41 | ### Building 42 | 43 | * `ember build` (development) 44 | * `ember build --environment production` (production) 45 | 46 | ### Deploying 47 | 48 | Specify what it takes to deploy your app. 49 | 50 | ## Further Reading / Useful Links 51 | 52 | * [ember.js](https://emberjs.com/) 53 | * [ember-cli](https://cli.emberjs.com/release/) 54 | * Development Browser Extensions 55 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 56 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 57 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'test-app-ts/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app-ts/app/components/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/components/use-machine.hbs: -------------------------------------------------------------------------------- 1 |
2 |

3 | use-machine 4 |

5 | 8 |
9 | {{#if this.isOn}} 10 | On 11 | {{/if}} 12 | {{#if this.isOff}} 13 | Off 14 | {{/if}} 15 |
16 |
-------------------------------------------------------------------------------- /test-apps/test-app-ts/app/components/use-machine.ts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { useMachine } from 'ember-statecharts'; 3 | import toggleMachine from '../machines/toggle'; 4 | 5 | export default class UseMachineTest extends Component { 6 | statechart = useMachine(this, () => { 7 | return { 8 | machine: toggleMachine, 9 | }; 10 | }); 11 | 12 | get isOn() { 13 | return this.statechart.state?.matches('on'); 14 | } 15 | 16 | get isOff() { 17 | return this.statechart.state?.matches('off'); 18 | } 19 | 20 | toggle = () => { 21 | this.statechart.send('TOGGLE'); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/config/environment.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type declarations for 3 | * import config from 'my-app/config/environment' 4 | */ 5 | declare const config: { 6 | environment: string; 7 | modulePrefix: string; 8 | podModulePrefix: string; 9 | locationType: 'history' | 'hash' | 'none' | 'auto'; 10 | rootURL: string; 11 | APP: Record; 12 | }; 13 | 14 | export default config; 15 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app-ts/app/controllers/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app-ts/app/helpers/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestAppTs 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 14 | {{content-for "head-footer"}} 15 | 16 | 17 | {{content-for "body"}} 18 | 19 | 20 | 21 | 22 | {{content-for "body-footer"}} 23 | 24 | 25 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/machines/toggle.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export interface ToggleContext {} 4 | 5 | export type ToggleEvent = { type: 'TOGGLE' }; 6 | 7 | export default createMachine({ 8 | initial: 'off', 9 | states: { 10 | on: { 11 | on: { 12 | TOGGLE: 'off', 13 | }, 14 | }, 15 | off: { 16 | on: { 17 | TOGGLE: 'on', 18 | }, 19 | }, 20 | }, 21 | predictableActionArguments: true, 22 | }); 23 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app-ts/app/models/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'test-app-ts/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app-ts/app/routes/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app-ts/app/styles/app.css -------------------------------------------------------------------------------- /test-apps/test-app-ts/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{page-title 'TestAppTs'}} 2 | 3 |

Welcome to Test-App-TS

4 | 5 | 6 | 7 | {{outlet}} -------------------------------------------------------------------------------- /test-apps/test-app-ts/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "4.8.0", 7 | "blueprints": [ 8 | { 9 | "name": "app", 10 | "outputRepo": "https://github.com/ember-cli/ember-new-output", 11 | "codemodsSource": "ember-app-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--no-welcome", 15 | "--ci-provider=github" 16 | ] 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'test-app-ts', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 13 | }, 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | }, 20 | }; 21 | 22 | if (environment === 'development') { 23 | // ENV.APP.LOG_RESOLVER = true; 24 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 25 | // ENV.APP.LOG_TRANSITIONS = true; 26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 27 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 28 | } 29 | 30 | if (environment === 'test') { 31 | // Testem prefers this... 32 | ENV.locationType = 'none'; 33 | 34 | // keep test console output quieter 35 | ENV.APP.LOG_ACTIVE_GENERATION = false; 36 | ENV.APP.LOG_VIEW_LOOKUPS = false; 37 | 38 | ENV.APP.rootElement = '#ember-testing'; 39 | ENV.APP.autoboot = false; 40 | } 41 | 42 | if (environment === 'production') { 43 | // here you can enable a production-specific feature 44 | } 45 | 46 | return ENV; 47 | }; 48 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | module.exports = { 10 | browsers, 11 | }; 12 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function (defaults) { 6 | const app = new EmberApp(defaults, { 7 | // Add options here 8 | }); 9 | 10 | // Use `app.import` to add additional libraries to the generated 11 | // output files. 12 | // 13 | // If you need to use different assets in different 14 | // environments, specify an object as the first parameter. That 15 | // object's keys should be the environment name and the values 16 | // should be the asset to use in that environment. 17 | // 18 | // If the library that you are including contains AMD or ES6 19 | // modules that you would like to import into your application 20 | // please specify an object with the list of modules as keys 21 | // along with the exports of each module as its value. 22 | 23 | return app.toTree(); 24 | }; 25 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-app-ts", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Small description for test-app-ts goes here", 6 | "repository": "", 7 | "license": "MIT", 8 | "author": "", 9 | "directories": { 10 | "doc": "doc", 11 | "test": "tests" 12 | }, 13 | "scripts": { 14 | "build": "ember build --environment=production", 15 | "lint": "npm-run-all --print-name --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", 16 | "lint:fix": "npm-run-all --print-name --aggregate-output --continue-on-error --parallel \"lint:*:fix\"", 17 | "lint:hbs": "ember-template-lint .", 18 | "lint:hbs:fix": "ember-template-lint . --fix", 19 | "lint:js": "eslint . --cache", 20 | "lint:js:fix": "eslint . --fix", 21 | "start": "ember serve", 22 | "test": "npm-run-all --print-name \"lint\" \"test:*\"", 23 | "test:ember": "ember test --test-port=0" 24 | }, 25 | "devDependencies": { 26 | "@ember/optional-features": "^2.0.0", 27 | "@ember/test-helpers": "^2.8.1", 28 | "@glimmer/component": "^1.1.2", 29 | "@glimmer/tracking": "^1.1.2", 30 | "@tsconfig/ember": "^1.0.1", 31 | "@types/ember": "^4.0.2", 32 | "@types/ember-data": "^4.4.6", 33 | "@types/ember-data__adapter": "^4.0.1", 34 | "@types/ember-data__model": "^4.0.0", 35 | "@types/ember-data__serializer": "^4.0.1", 36 | "@types/ember-data__store": "^4.0.2", 37 | "@types/ember-qunit": "^5.0.2", 38 | "@types/ember-resolver": "^5.0.13", 39 | "@types/ember__application": "^4.0.4", 40 | "@types/ember__array": "^4.0.3", 41 | "@types/ember__component": "^4.0.11", 42 | "@types/ember__controller": "^4.0.3", 43 | "@types/ember__debug": "^4.0.3", 44 | "@types/ember__destroyable": "^4.0.1", 45 | "@types/ember__engine": "^4.0.4", 46 | "@types/ember__error": "^4.0.1", 47 | "@types/ember__object": "^4.0.5", 48 | "@types/ember__polyfills": "^4.0.1", 49 | "@types/ember__routing": "^4.0.12", 50 | "@types/ember__runloop": "^4.0.2", 51 | "@types/ember__service": "^4.0.1", 52 | "@types/ember__string": "^3.0.10", 53 | "@types/ember__template": "^4.0.1", 54 | "@types/ember__test": "^4.0.1", 55 | "@types/ember__test-helpers": "^2.8.2", 56 | "@types/ember__utils": "^4.0.2", 57 | "@types/qunit": "^2.19.3", 58 | "@types/rsvp": "^4.0.4", 59 | "babel-eslint": "^10.1.0", 60 | "broccoli-asset-rev": "^3.0.0", 61 | "ember-auto-import": "^2.4.3", 62 | "ember-cli": "~4.8.0", 63 | "ember-cli-app-version": "^5.0.0", 64 | "ember-cli-babel": "^7.26.11", 65 | "ember-cli-dependency-checker": "^3.3.1", 66 | "ember-cli-htmlbars": "^6.1.1", 67 | "ember-cli-inject-live-reload": "^2.1.0", 68 | "ember-cli-sri": "^2.1.1", 69 | "ember-cli-terser": "^4.0.2", 70 | "ember-cli-typescript": "^5.2.1", 71 | "ember-data": "~4.7.3", 72 | "ember-fetch": "^8.1.2", 73 | "ember-load-initializers": "^2.1.2", 74 | "ember-page-title": "^7.0.0", 75 | "ember-qunit": "^6.0.0", 76 | "ember-resolver": "^8.0.3", 77 | "ember-resources": "^5.6.0", 78 | "ember-source": "~4.8.0", 79 | "ember-statecharts": "workspace:*", 80 | "ember-template-lint": "^4.16.1", 81 | "eslint": "^7.32.0", 82 | "eslint-config-prettier": "^8.5.0", 83 | "eslint-plugin-ember": "^11.1.0", 84 | "eslint-plugin-node": "^11.1.0", 85 | "eslint-plugin-prettier": "^4.2.1", 86 | "eslint-plugin-qunit": "^7.3.1", 87 | "loader.js": "^4.7.0", 88 | "npm-run-all": "^4.1.5", 89 | "prettier": "^2.7.1", 90 | "qunit": "^2.19.2", 91 | "qunit-dom": "^2.0.0", 92 | "typescript": "^4.9.3", 93 | "webpack": "^5.74.0", 94 | "xstate": "^4.34.0" 95 | }, 96 | "engines": { 97 | "node": "14.* || 16.* || >= 18" 98 | }, 99 | "ember": { 100 | "edition": "octane" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | } from 'ember-qunit'; 6 | 7 | // This file exists to provide wrappers around ember-qunit's / ember-mocha's 8 | // test setup functions. This way, you can easily extend the setup that is 9 | // needed per test type. 10 | 11 | function setupApplicationTest(hooks, options) { 12 | upstreamSetupApplicationTest(hooks, options); 13 | 14 | // Additional setup for application tests can be done here. 15 | // 16 | // For example, if you need an authenticated session for each 17 | // application test, you could do: 18 | // 19 | // hooks.beforeEach(async function () { 20 | // await authenticateSession(); // ember-simple-auth 21 | // }); 22 | // 23 | // This is also a good place to call test setup functions coming 24 | // from other addons: 25 | // 26 | // setupIntl(hooks); // ember-intl 27 | // setupMirage(hooks); // ember-cli-mirage 28 | } 29 | 30 | function setupRenderingTest(hooks, options) { 31 | upstreamSetupRenderingTest(hooks, options); 32 | 33 | // Additional setup for rendering tests can be done here. 34 | } 35 | 36 | function setupTest(hooks, options) { 37 | upstreamSetupTest(hooks, options); 38 | 39 | // Additional setup for unit tests can be done here. 40 | } 41 | 42 | export { setupApplicationTest, setupRenderingTest, setupTest }; 43 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestAppTs Tests 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} 17 | {{content-for "test-head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | {{content-for "test-body"}} 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{content-for "body-footer"}} 37 | {{content-for "test-body-footer"}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app-ts/tests/integration/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app-ts/tests/integration/components/use-machine-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'test-app-ts/tests/helpers'; 3 | import { click, render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | module('Integration | Component | use-machine-ts', function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('smoke test', async function (assert) { 10 | await render(hbs``); 11 | 12 | assert 13 | .dom('[data-test-content]') 14 | .hasText('Off', 'Initially the state is `off`'); 15 | 16 | await click('[data-test-toggle-button]'); 17 | 18 | assert 19 | .dom('[data-test-content]') 20 | .hasText('On', 'After clicking the toggle the state is `on`'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'test-app-ts/app'; 2 | import config from 'test-app-ts/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setApplication(Application.create(config.APP)); 9 | 10 | setup(QUnit.assert); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app-ts/tests/unit/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember/tsconfig.json", 3 | "compilerOptions": { 4 | 5 | // The combination of `baseUrl` with `paths` allows Ember's classic package 6 | // layout, which is not resolvable with the Node resolution algorithm, to 7 | // work with TypeScript. 8 | "baseUrl": ".", 9 | "paths": { 10 | "test-app-ts/tests/*": [ 11 | "tests/*" 12 | ], 13 | "test-app-ts/*": [ 14 | "app/*" 15 | ], 16 | "*": [ 17 | "types/*" 18 | ] 19 | } 20 | }, 21 | "include": [ 22 | "app/**/*", 23 | "tests/**/*", 24 | "types/**/*" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/types/ember-data/types/registries/model.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Catch-all for ember-data. 3 | */ 4 | export default interface ModelRegistry { 5 | [key: string]: any; 6 | } 7 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/types/global.d.ts: -------------------------------------------------------------------------------- 1 | // Types for compiled templates 2 | declare module 'test-app-ts/templates/*' { 3 | import { TemplateFactory } from 'ember-cli-htmlbars'; 4 | 5 | const tmpl: TemplateFactory; 6 | export default tmpl; 7 | } 8 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/types/test-app-ts/index.d.ts: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | declare global { 4 | // Prevents ESLint from "fixing" this via its auto-fix to turn it into a type 5 | // alias (e.g. after running any Ember CLI generator) 6 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 7 | interface Array extends Ember.ArrayPrototypeExtensions {} 8 | // interface Function extends Ember.FunctionPrototypeExtensions {} 9 | } 10 | 11 | export {}; 12 | -------------------------------------------------------------------------------- /test-apps/test-app-ts/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app-ts/vendor/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /test-apps/test-app/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false, 9 | 10 | /** 11 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript 12 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available. 13 | */ 14 | "isTypeScriptProject": false 15 | } 16 | -------------------------------------------------------------------------------- /test-apps/test-app/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /test-apps/test-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | ecmaVersion: 2018, 8 | sourceType: 'module', 9 | ecmaFeatures: { 10 | legacyDecorators: true, 11 | }, 12 | }, 13 | plugins: ['ember'], 14 | extends: [ 15 | 'eslint:recommended', 16 | 'plugin:ember/recommended', 17 | 'plugin:prettier/recommended', 18 | ], 19 | env: { 20 | browser: true, 21 | }, 22 | rules: {}, 23 | overrides: [ 24 | // node files 25 | { 26 | files: [ 27 | './.eslintrc.js', 28 | './.prettierrc.js', 29 | './.template-lintrc.js', 30 | './ember-cli-build.js', 31 | './testem.js', 32 | './blueprints/*/index.js', 33 | './config/**/*.js', 34 | './lib/*/index.js', 35 | './server/**/*.js', 36 | ], 37 | parserOptions: { 38 | sourceType: 'script', 39 | }, 40 | env: { 41 | browser: false, 42 | node: true, 43 | }, 44 | plugins: ['node'], 45 | extends: ['plugin:node/recommended'], 46 | rules: { 47 | // this can be removed once the following is fixed 48 | // https://github.com/mysticatea/eslint-plugin-node/issues/77 49 | 'node/no-unpublished-require': 'off', 50 | }, 51 | }, 52 | { 53 | // test files 54 | files: ['tests/**/*-test.{js,ts}'], 55 | extends: ['plugin:qunit/recommended'], 56 | }, 57 | ], 58 | }; 59 | -------------------------------------------------------------------------------- /test-apps/test-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /.eslintcache 16 | /connect.lock 17 | /coverage/ 18 | /libpeerconnection.log 19 | /npm-debug.log* 20 | /testem.log 21 | /yarn-error.log 22 | 23 | # ember-try 24 | /.node_modules.ember-try/ 25 | /bower.json.ember-try 26 | /npm-shrinkwrap.json.ember-try 27 | /package.json.ember-try 28 | /package-lock.json.ember-try 29 | /yarn.lock.ember-try 30 | 31 | # broccoli-debug 32 | /DEBUG/ 33 | -------------------------------------------------------------------------------- /test-apps/test-app/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | .lint-todo/ 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /test-apps/test-app/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | }; 6 | -------------------------------------------------------------------------------- /test-apps/test-app/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /test-apps/test-app/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/test-app/README.md: -------------------------------------------------------------------------------- 1 | # test-app 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | * [Git](https://git-scm.com/) 11 | * [Node.js](https://nodejs.org/) (with npm) 12 | * [Ember CLI](https://cli.emberjs.com/release/) 13 | * [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | * `git clone ` this repository 18 | * `cd test-app` 19 | * `npm install` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | * `ember test` 34 | * `ember test --server` 35 | 36 | ### Linting 37 | 38 | * `npm run lint` 39 | * `npm run lint:fix` 40 | 41 | ### Building 42 | 43 | * `ember build` (development) 44 | * `ember build --environment production` (production) 45 | 46 | ### Deploying 47 | 48 | Specify what it takes to deploy your app. 49 | 50 | ## Further Reading / Useful Links 51 | 52 | * [ember.js](https://emberjs.com/) 53 | * [ember-cli](https://cli.emberjs.com/release/) 54 | * Development Browser Extensions 55 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 56 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 57 | -------------------------------------------------------------------------------- /test-apps/test-app/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'test-app/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /test-apps/test-app/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app/app/components/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app/app/components/delayed-toggle.hbs: -------------------------------------------------------------------------------- 1 | 8 | {{#if this.isOn}} 9 |
On
10 | {{else}} 11 |
Off
12 | {{/if}} -------------------------------------------------------------------------------- /test-apps/test-app/app/components/delayed-toggle.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { useMachine } from 'ember-statecharts'; 3 | import delayedToggle from '../machines/delayed-toggle'; 4 | 5 | export default class DelayedToggle extends Component { 6 | statechart = useMachine(this, () => { 7 | return { 8 | machine: delayedToggle, 9 | }; 10 | }); 11 | 12 | get isOn() { 13 | return this.statechart.state.matches('on'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-apps/test-app/app/components/self-transition-assign.hbs: -------------------------------------------------------------------------------- 1 |
2 |

3 | Self-transition 4 |

5 | 8 |
9 | {{this.numberOfChanges}} 10 |
11 |
12 | {{this.count}} 13 |
14 |
-------------------------------------------------------------------------------- /test-apps/test-app/app/components/self-transition-assign.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { useMachine } from 'ember-statecharts'; 3 | import selfTransitionAssign from '../machines/self-transition-assign'; 4 | 5 | export default class SelfTransitionTest extends Component { 6 | statechart = useMachine(this, () => { 7 | return { 8 | machine: selfTransitionAssign, 9 | }; 10 | }); 11 | 12 | self = () => { 13 | this.statechart.send('SELF'); 14 | }; 15 | 16 | number = 0; 17 | 18 | get numberOfChanges() { 19 | this.number++; 20 | // consume the state tracked property 21 | this.statechart.state; 22 | return this.number; 23 | } 24 | 25 | get count() { 26 | return this.statechart.state.context.count; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test-apps/test-app/app/components/self-transition.hbs: -------------------------------------------------------------------------------- 1 |
2 |

3 | Self-transition 4 |

5 | 8 |
9 | {{this.numberOfChanges}} 10 |
11 |
-------------------------------------------------------------------------------- /test-apps/test-app/app/components/self-transition.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { useMachine } from 'ember-statecharts'; 3 | import selfTransitionNoAssign from '../machines/self-transition-no-assign'; 4 | 5 | export default class SelfTransitionTest extends Component { 6 | statechart = useMachine(this, () => { 7 | return { 8 | machine: selfTransitionNoAssign, 9 | }; 10 | }); 11 | 12 | self = () => { 13 | this.statechart.send('SELF'); 14 | }; 15 | 16 | number = 0; 17 | 18 | get numberOfChanges() { 19 | this.number++; 20 | // consume the state tracked property 21 | this.statechart.state; 22 | return this.number; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test-apps/test-app/app/components/use-machine.hbs: -------------------------------------------------------------------------------- 1 |
2 |

3 | use-machine 4 |

5 | 8 |
9 | {{#if this.isOn}} 10 | On 11 | {{/if}} 12 | {{#if this.isOff}} 13 | Off 14 | {{/if}} 15 |
16 |
-------------------------------------------------------------------------------- /test-apps/test-app/app/components/use-machine.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { useMachine } from 'ember-statecharts'; 3 | import toggleMachine from '../machines/toggle'; 4 | 5 | export default class UseMachineTest extends Component { 6 | statechart = useMachine(this, () => { 7 | return { 8 | machine: toggleMachine, 9 | }; 10 | }); 11 | 12 | get isOn() { 13 | return this.statechart.state.matches('on'); 14 | } 15 | 16 | get isOff() { 17 | return this.statechart.state.matches('off'); 18 | } 19 | 20 | toggle = () => { 21 | this.statechart.send('TOGGLE'); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /test-apps/test-app/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app/app/controllers/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app/app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { createMachine } from 'xstate'; 3 | import { useMachine } from 'ember-statecharts'; 4 | 5 | const toggleMachine = createMachine({ 6 | predictableActionArguments: true, 7 | initial: 'off', 8 | states: { 9 | on: { 10 | on: { 11 | TOGGLE: 'off', 12 | }, 13 | }, 14 | off: { 15 | on: { 16 | TOGGLE: 'on', 17 | }, 18 | }, 19 | }, 20 | }); 21 | 22 | export default class ApplicationController extends Controller { 23 | statechart = useMachine(this, () => { 24 | return { 25 | machine: toggleMachine, 26 | }; 27 | }); 28 | 29 | toggle = () => { 30 | this.statechart.send('TOGGLE'); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /test-apps/test-app/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app/app/helpers/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 14 | {{content-for "head-footer"}} 15 | 16 | 17 | {{content-for "body"}} 18 | 19 | 20 | 21 | 22 | {{content-for "body-footer"}} 23 | 24 | 25 | -------------------------------------------------------------------------------- /test-apps/test-app/app/machines/delayed-toggle.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export default createMachine({ 4 | initial: 'off', 5 | states: { 6 | off: { 7 | on: { 8 | TOGGLE: 'on', 9 | }, 10 | }, 11 | on: { 12 | after: { 13 | 500: 'off', 14 | }, 15 | }, 16 | }, 17 | predictableActionArguments: true, 18 | }); 19 | -------------------------------------------------------------------------------- /test-apps/test-app/app/machines/self-transition-assign.js: -------------------------------------------------------------------------------- 1 | import { createMachine, assign } from 'xstate'; 2 | 3 | export default createMachine({ 4 | initial: 'active', 5 | 6 | context: { 7 | count: 0, 8 | }, 9 | 10 | states: { 11 | active: { 12 | on: { 13 | SELF: { 14 | actions: [ 15 | assign({ 16 | count: (context) => context.count + 1, 17 | }), 18 | ], 19 | }, 20 | }, 21 | }, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /test-apps/test-app/app/machines/self-transition-no-assign.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export default createMachine({ 4 | initial: 'active', 5 | 6 | states: { 7 | active: { 8 | on: { 9 | SELF: {}, 10 | }, 11 | }, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /test-apps/test-app/app/machines/toggle.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export const toggleConfig = { 4 | initial: 'off', 5 | states: { 6 | off: { 7 | on: { 8 | TOGGLE: 'on', 9 | }, 10 | }, 11 | on: { 12 | on: { 13 | TOGGLE: 'off', 14 | }, 15 | }, 16 | }, 17 | }; 18 | 19 | export default createMachine(toggleConfig); 20 | -------------------------------------------------------------------------------- /test-apps/test-app/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app/app/models/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'test-app/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | -------------------------------------------------------------------------------- /test-apps/test-app/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app/app/routes/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app/app/styles/app.css -------------------------------------------------------------------------------- /test-apps/test-app/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{page-title 'TestApp'}} 2 | 3 |

Welcome to Test-App

4 | 5 | 6 | 7 | {{outlet}} -------------------------------------------------------------------------------- /test-apps/test-app/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "4.8.0", 7 | "blueprints": [ 8 | { 9 | "name": "app", 10 | "outputRepo": "https://github.com/ember-cli/ember-new-output", 11 | "codemodsSource": "ember-app-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--no-welcome", 15 | "--ci-provider=travis" 16 | ] 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /test-apps/test-app/config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 5 | 6 | module.exports = async function () { 7 | return { 8 | usePnpm: true, 9 | scenarios: [ 10 | { 11 | name: 'ember-lts-3.28', 12 | npm: { 13 | devDependencies: { 14 | 'ember-source': '~3.28.0', 15 | }, 16 | }, 17 | }, 18 | { 19 | name: 'ember-lts-4.4', 20 | npm: { 21 | devDependencies: { 22 | 'ember-source': '~4.4.0', 23 | }, 24 | }, 25 | }, 26 | { 27 | name: 'ember-release', 28 | npm: { 29 | devDependencies: { 30 | 'ember-source': await getChannelURL('release'), 31 | }, 32 | }, 33 | }, 34 | { 35 | name: 'ember-beta', 36 | npm: { 37 | devDependencies: { 38 | 'ember-source': await getChannelURL('beta'), 39 | }, 40 | }, 41 | }, 42 | { 43 | name: 'ember-canary', 44 | npm: { 45 | devDependencies: { 46 | 'ember-source': await getChannelURL('canary'), 47 | }, 48 | }, 49 | }, 50 | { 51 | name: 'ember-classic', 52 | env: { 53 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 54 | 'application-template-wrapper': true, 55 | 'default-async-observers': false, 56 | 'template-only-glimmer-components': false, 57 | }), 58 | }, 59 | npm: { 60 | devDependencies: { 61 | 'ember-source': '~3.28.0', 62 | }, 63 | ember: { 64 | edition: 'classic', 65 | }, 66 | }, 67 | }, 68 | embroiderSafe(), 69 | embroiderOptimized(), 70 | ], 71 | }; 72 | }; 73 | -------------------------------------------------------------------------------- /test-apps/test-app/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'test-app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 13 | }, 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | }, 20 | }; 21 | 22 | if (environment === 'development') { 23 | // ENV.APP.LOG_RESOLVER = true; 24 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 25 | // ENV.APP.LOG_TRANSITIONS = true; 26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 27 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 28 | } 29 | 30 | if (environment === 'test') { 31 | // Testem prefers this... 32 | ENV.locationType = 'none'; 33 | 34 | // keep test console output quieter 35 | ENV.APP.LOG_ACTIVE_GENERATION = false; 36 | ENV.APP.LOG_VIEW_LOOKUPS = false; 37 | 38 | ENV.APP.rootElement = '#ember-testing'; 39 | ENV.APP.autoboot = false; 40 | } 41 | 42 | if (environment === 'production') { 43 | // here you can enable a production-specific feature 44 | } 45 | 46 | return ENV; 47 | }; 48 | -------------------------------------------------------------------------------- /test-apps/test-app/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /test-apps/test-app/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | module.exports = { 10 | browsers, 11 | }; 12 | -------------------------------------------------------------------------------- /test-apps/test-app/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function (defaults) { 6 | let app = new EmberApp(defaults, { 7 | autoImport: { 8 | watchDependencies: ['ember-statecharts'], 9 | }, 10 | }); 11 | 12 | const { maybeEmbroider } = require('@embroider/test-setup'); 13 | return maybeEmbroider(app); 14 | }; 15 | -------------------------------------------------------------------------------- /test-apps/test-app/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true 4 | } 5 | } -------------------------------------------------------------------------------- /test-apps/test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Small description for test-app goes here", 6 | "repository": "", 7 | "license": "MIT", 8 | "author": "", 9 | "directories": { 10 | "doc": "doc", 11 | "test": "tests" 12 | }, 13 | "scripts": { 14 | "build": "ember build --environment=production", 15 | "lint": "npm-run-all --print-name --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", 16 | "lint:fix": "npm-run-all --print-name --aggregate-output --continue-on-error --parallel \"lint:*:fix\"", 17 | "lint:hbs": "ember-template-lint .", 18 | "lint:hbs:fix": "ember-template-lint . --fix", 19 | "lint:js": "eslint . --cache", 20 | "lint:js:fix": "eslint . --fix", 21 | "start": "ember serve", 22 | "test": "npm-run-all lint \"test:!(watch)\"", 23 | "test:ember": "ember test", 24 | "test:watch": "ember test --server" 25 | }, 26 | "devDependencies": { 27 | "@ember/optional-features": "^2.0.0", 28 | "@ember/test-helpers": "^2.8.1", 29 | "@embroider/test-setup": "^1.7.1", 30 | "@glimmer/component": "^1.1.2", 31 | "@glimmer/tracking": "^1.1.2", 32 | "babel-eslint": "^10.1.0", 33 | "broccoli-asset-rev": "^3.0.0", 34 | "ember-auto-import": "^2.4.3", 35 | "ember-cli": "~4.8.0", 36 | "ember-cli-app-version": "^5.0.0", 37 | "ember-cli-babel": "^7.26.11", 38 | "ember-cli-dependency-checker": "^3.3.1", 39 | "ember-cli-htmlbars": "^6.1.1", 40 | "ember-cli-inject-live-reload": "^2.1.0", 41 | "ember-cli-sri": "^2.1.1", 42 | "ember-cli-terser": "^4.0.2", 43 | "ember-data": "~4.7.3", 44 | "ember-disable-prototype-extensions": "^1.1.3", 45 | "ember-fetch": "^8.1.2", 46 | "ember-load-initializers": "^2.1.2", 47 | "ember-page-title": "^7.0.0", 48 | "ember-qunit": "^6.0.0", 49 | "ember-resolver": "^8.0.3", 50 | "ember-resources": "^5.6.0", 51 | "ember-source": "~4.8.0", 52 | "ember-source-channel-url": "^3.0.0", 53 | "ember-statecharts": "workspace:*", 54 | "ember-template-lint": "^4.16.1", 55 | "ember-try": "^2.0.0", 56 | "eslint": "^7.32.0", 57 | "eslint-config-prettier": "^8.5.0", 58 | "eslint-plugin-ember": "^11.1.0", 59 | "eslint-plugin-node": "^11.1.0", 60 | "eslint-plugin-prettier": "^4.2.1", 61 | "eslint-plugin-qunit": "^7.3.1", 62 | "loader.js": "^4.7.0", 63 | "npm-run-all": "^4.1.5", 64 | "prettier": "^2.7.1", 65 | "qunit": "^2.19.2", 66 | "qunit-dom": "^2.0.0", 67 | "webpack": "^5.74.0", 68 | "xstate": "^4.34.0" 69 | }, 70 | "engines": { 71 | "node": "14.* || 16.* || >= 18" 72 | }, 73 | "ember": { 74 | "edition": "octane" 75 | } 76 | } -------------------------------------------------------------------------------- /test-apps/test-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test-apps/test-app/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /test-apps/test-app/tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | } from 'ember-qunit'; 6 | 7 | // This file exists to provide wrappers around ember-qunit's / ember-mocha's 8 | // test setup functions. This way, you can easily extend the setup that is 9 | // needed per test type. 10 | 11 | function setupApplicationTest(hooks, options) { 12 | upstreamSetupApplicationTest(hooks, options); 13 | 14 | // Additional setup for application tests can be done here. 15 | // 16 | // For example, if you need an authenticated session for each 17 | // application test, you could do: 18 | // 19 | // hooks.beforeEach(async function () { 20 | // await authenticateSession(); // ember-simple-auth 21 | // }); 22 | // 23 | // This is also a good place to call test setup functions coming 24 | // from other addons: 25 | // 26 | // setupIntl(hooks); // ember-intl 27 | // setupMirage(hooks); // ember-cli-mirage 28 | } 29 | 30 | function setupRenderingTest(hooks, options) { 31 | upstreamSetupRenderingTest(hooks, options); 32 | 33 | // Additional setup for rendering tests can be done here. 34 | } 35 | 36 | function setupTest(hooks, options) { 37 | upstreamSetupTest(hooks, options); 38 | 39 | // Additional setup for unit tests can be done here. 40 | } 41 | 42 | export { setupApplicationTest, setupRenderingTest, setupTest }; 43 | -------------------------------------------------------------------------------- /test-apps/test-app/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp Tests 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} 17 | {{content-for "test-head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | {{content-for "test-body"}} 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{content-for "body-footer"}} 37 | {{content-for "test-body-footer"}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /test-apps/test-app/tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app/tests/integration/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app/tests/integration/components/delayed-toggle-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'test-app/tests/helpers'; 3 | import { click, render, waitUntil } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | module('Integration | Component | use-machine', function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('awaiting needs to be explicit with no `runloopClockService`-option set', async function (assert) { 10 | const config = this.owner.resolveRegistration('config:environment'); 11 | 12 | config['ember-statecharts'] = { 13 | runloopClockService: false, 14 | }; 15 | 16 | await render(hbs``); 17 | 18 | assert.dom('[data-test-off]').exists('initially toggle is off'); 19 | 20 | await click('[data-test-toggle]'); 21 | 22 | await waitUntil(() => { 23 | return document.querySelector('[data-test-off]'); 24 | }); 25 | 26 | assert.dom('[data-test-off]').exists('initially toggle is off'); 27 | }); 28 | 29 | test('awaiting happens automatically when `runloopClockService`-option is set', async function (assert) { 30 | const config = this.owner.resolveRegistration('config:environment'); 31 | 32 | config['ember-statecharts'] = { 33 | runloopClockService: true, 34 | }; 35 | 36 | await render(hbs``); 37 | 38 | assert.dom('[data-test-off]').exists('initially toggle is off'); 39 | 40 | await click('[data-test-toggle]'); 41 | 42 | assert.dom('[data-test-off]').exists('initially toggle is off'); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test-apps/test-app/tests/integration/components/use-machine-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'test-app/tests/helpers'; 3 | import { click, render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | module('Integration | Component | use-machine', function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('smoke test', async function (assert) { 10 | await render(hbs``); 11 | 12 | assert 13 | .dom('[data-test-content]') 14 | .hasText('Off', 'Initially the state is `off`'); 15 | 16 | await click('[data-test-toggle-button]'); 17 | 18 | assert 19 | .dom('[data-test-content]') 20 | .hasText('On', 'After clicking the toggle the state is `on`'); 21 | }); 22 | 23 | test('self-transitions do not trigger reactive getters', async function (assert) { 24 | await render(hbs``); 25 | 26 | assert 27 | .dom('[data-test-number-of-changes]') 28 | .hasText('1', 'Initially only 1 call'); 29 | 30 | await click('[data-test-self-button]'); 31 | 32 | assert 33 | .dom('[data-test-number-of-changes]') 34 | .hasText('1', 'It should still have 1 call'); 35 | }); 36 | 37 | test('self-transitions that assign to context trigger reactive getters', async function (assert) { 38 | await render(hbs``); 39 | 40 | assert 41 | .dom('[data-test-number-of-changes]') 42 | .hasText('1', 'Initially only 1 call'); 43 | 44 | assert.dom('[data-test-context-count]').hasText('0', 'count starts at 0'); 45 | 46 | await click('[data-test-self-button]'); 47 | 48 | assert 49 | .dom('[data-test-context-count]') 50 | .hasText('1', 'Context was updated via self-transition'); 51 | 52 | assert 53 | .dom('[data-test-number-of-changes]') 54 | .hasText( 55 | '2', 56 | 'reactive getter was updated because we assigned to context' 57 | ); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test-apps/test-app/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'test-app/app'; 2 | import config from 'test-app/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setApplication(Application.create(config.APP)); 9 | 10 | setup(QUnit.assert); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /test-apps/test-app/tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app/tests/unit/.gitkeep -------------------------------------------------------------------------------- /test-apps/test-app/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LevelbossMike/ember-statecharts/caa6c7324c4a3b3019004169103bd79d1ec4b416/test-apps/test-app/vendor/.gitkeep --------------------------------------------------------------------------------