-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Documentation #19
Merged
Documentation #19
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
4287ad3
docs: set up AddonDocs
dfreeman 81b5b74
docs: introduce MilestonesPlayground component
dfreeman 6d49c18
docs: write actual documentation
dfreeman eb85616
docs: add test to ensure all playgrounds run to completion
dfreeman a10f646
docs: add READMEs for each package and at the repo root
dfreeman e12a23d
docs: PR feedback
dfreeman 27958ef
docs: fix a stray string key
dfreeman 73842a8
docs: fix typos
dfreeman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/packages/deploy* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Milestones [](https://travis-ci.org/salsify/milestones) | ||
|
||
The `@milestones` packages provide a set of tools for navigating concurrent code in testing and development. Milestones act as named synchronization points, and they give you the ability to change the behavior of annotated code during testing, skipping pauses or mocking out results. | ||
|
||
Full interactive documentation can be found at https://salsify.github.io/milestones. | ||
|
||
## Packages | ||
|
||
This library is broken out into several packages: | ||
- [@milestones/core](packages/core): the core library, containing tools for defining and interacting with milestones | ||
- [@milestones/babel-plugin-strip-milestones](packages/babel-plugin-strip-milestones): a Babel plugin that removes milestone definitions from your code, ensuring no overhead in production | ||
- [@milestones/ember](packages/ember): an Ember addon that integrates with the framework runtime and build system to provide zero-config setup for working with milestones |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# @milestones/babel-plugin-strip-milestones | ||
|
||
This package allows you to strip the `milestone()` calls completely out of your code to eliminate any overhead in production. | ||
|
||
Full documentation can be found at https://salsify.github.io/milestones/docs/babel-plugin. | ||
|
||
## Usage | ||
|
||
Include `@milestones/babel-plugin-strip-milestones` in your package dependencies and then add `'@milestones/babel-plugin-strip-milestones'` to the `plugins` array in your `babel.config.json` or the corresponding configuration location for your particular Babel integration. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# @milestones/core | ||
|
||
Milestones are a tool for annotating points in time in your code. | ||
|
||
A milestone can act as a named synchronization point, allowing you to ensure the state of your application code at the moment in time in your testing when you wish to make assertions. They also give you the ability to change the behavior of annotated code during testing, skipping pauses or mocking out results. | ||
|
||
Full interactive documentation can be found at https://salsify.github.io/milestones. | ||
|
||
## Example | ||
|
||
#### Application Code | ||
```ts | ||
import { milestone } from '@milestones/core'; | ||
|
||
export const Save = Symbol('save'); | ||
export const ShowCompletion = Symbol('show-completion-message'); | ||
|
||
// ... | ||
|
||
async function save() { | ||
renderMessage('Saving...'); | ||
await milestone(Save, () => saveData()); | ||
|
||
renderMessage('Saved!'); | ||
await milestone(ShowCompletion, () => sleep(4000)); | ||
|
||
renderMessage(''); | ||
} | ||
``` | ||
|
||
#### Test Code | ||
```ts | ||
import { advanceTo } from '@milestones/core'; | ||
import { Save, ShowCompletion } from '../app/code/under/test'; | ||
|
||
// ... | ||
|
||
it('renders the current saving status', async () => { | ||
click('.save-button'); | ||
|
||
// Wait until we start saving, then check that | ||
// the expected message is being shown. | ||
let saveHandle = await advanceTo(Save); | ||
expect(currentMessage()).to.equal('Saving...'); | ||
|
||
// Now go ahead and perform the save | ||
await saveHandle.continue(); | ||
expect(currentMessage()).to.equal('Saved!'); | ||
|
||
// Now advance to where we pause to show the | ||
// the status message, but skip the sleep | ||
await advanceTo(ShowCompletion).andReturn(); | ||
expect(currentMessage()).to.equal(''); | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# @milestones/ember | ||
|
||
The `@milestones/ember` addon integrates with the Ember runtime and ember-cli to provide zero-config setup for working with milestones. | ||
|
||
Full documentation can be found at https://salsify.github.io/milestones/docs/ember. | ||
|
||
## Runtime | ||
|
||
All milestones are configured by `@milestones/ember` to use RSVP for their promise implementation, ensuring that any code that executes as a result of a milestone occurs within a runloop. | ||
|
||
## Build | ||
|
||
By default, milestones will be stripped from your code in production builds using `@milestones/babel-plugin-strip-milestones`. This behavior is always controlled by the host application, and it can be overridden in the host's `ember-cli-build.js`. | ||
|
||
```ts | ||
let app = new EmberApp(defaults, { | ||
milestones: { | ||
stripMilestones: false, // or whatever your preference | ||
}, | ||
}); | ||
``` | ||
|
||
## Ember Concurrency | ||
|
||
If `ember-concurrency` is present in your project, any milestones you create will be task-like promises that will bubble cancelation appropriately. They will also be cancelable from your test code. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/* eslint-env node */ | ||
'use strict'; | ||
|
||
// eslint-disable-next-line node/no-unpublished-require | ||
const AddonDocsConfig = require('ember-cli-addon-docs/lib/config'); | ||
|
||
module.exports = class extends AddonDocsConfig { | ||
// See https://ember-learn.github.io/ember-cli-addon-docs/docs/deploying | ||
// for details on configuration you can override here. | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* eslint-env node */ | ||
'use strict'; | ||
|
||
module.exports = function(deployTarget) { | ||
let ENV = { | ||
build: {}, | ||
// include other plugin configuration that applies to all deploy targets here | ||
}; | ||
|
||
if (deployTarget === 'development') { | ||
ENV.build.environment = 'development'; | ||
// configure other plugins for development deploy target here | ||
} | ||
|
||
if (deployTarget === 'staging') { | ||
ENV.build.environment = 'production'; | ||
// configure other plugins for staging deploy target here | ||
} | ||
|
||
if (deployTarget === 'production') { | ||
ENV.build.environment = 'production'; | ||
// configure other plugins for production deploy target here | ||
} | ||
|
||
// Note: if you need to build some configuration asynchronously, you can return | ||
// a promise that resolves with the ENV object instead of returning the | ||
// ENV object synchronously. | ||
return ENV; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { visit } from '@ember/test-helpers'; | ||
import { setupApplicationTest } from 'ember-qunit'; | ||
import { module, test } from 'qunit'; | ||
import Env from 'dummy/config/environment'; | ||
import Pretender from 'pretender'; | ||
import { deactivateAllMilestones, activateMilestones } from '@milestones/core'; | ||
import { StepComplete } from 'dummy/utils/evaluation'; | ||
|
||
interface DocsRoutesService { | ||
routeUrls: string[]; | ||
} | ||
|
||
if (!Env.STRIP_MILESTONES) { | ||
const MAX_STEPS = 50; | ||
const URLS = [ | ||
'/', | ||
'/docs', | ||
'/docs/interacting-with-milestones', | ||
'/docs/milestone-keys', | ||
'/docs/ember', | ||
'/docs/the-playground', | ||
]; | ||
|
||
module('Acceptance | playgrounds', function(hooks) { | ||
setupApplicationTest(hooks); | ||
|
||
hooks.afterEach(() => deactivateAllMilestones()); | ||
|
||
let pretender: Pretender; | ||
hooks.beforeEach(function() { | ||
pretender = new Pretender(function() { | ||
this.get('https://httpbin.org/get', req => { | ||
let args = (req as { params: unknown }).params; | ||
return [200, {}, JSON.stringify({ args })]; | ||
}); | ||
|
||
// @ts-ignore | ||
this.get('/docs/*', this.passthrough); | ||
this.get('/versions.json', () => [404, {}, '']); | ||
}); | ||
}); | ||
|
||
hooks.afterEach(function() { | ||
pretender.shutdown(); | ||
}); | ||
|
||
test('every docs page with playgrounds is tested', async function(assert) { | ||
await visit('/docs'); | ||
|
||
let docsRoutes = this.owner.lookup('service:docs-routes') as DocsRoutesService; | ||
for (let url of docsRoutes.routeUrls) { | ||
if (URLS.includes(url)) continue; | ||
|
||
await visit(url); | ||
|
||
assert.notOk(this.element.querySelector('[data-test="playground"]'), `${url} has no playgrounds on it`); | ||
} | ||
}); | ||
|
||
for (let url of URLS) { | ||
test(`every playground on ${url} runs to completion`, async function(assert) { | ||
await visit(url); | ||
|
||
playgrounds: for (let [i, playground] of this.element.querySelectorAll('[data-test="playground"]').entries()) { | ||
let stepButton = playground.querySelector('[data-test="step"]')! as HTMLButtonElement; | ||
let resetButton = playground.querySelector('[data-test="reset"]')! as HTMLButtonElement; | ||
let output = playground.querySelector('[data-test="output"]')! as HTMLDivElement; | ||
|
||
for (let step = 0; step < MAX_STEPS; step++) { | ||
stepButton.click(); | ||
let coordinator = activateMilestones([StepComplete]); | ||
await coordinator.advanceTo(StepComplete); | ||
coordinator.deactivateAll(); | ||
|
||
if (step === 0) { | ||
assert.notOk(resetButton.disabled, `Playground ${i} on doesn't immediately error`); | ||
} | ||
|
||
if (resetButton.disabled) { | ||
assert.notOk(output.innerText.includes('Uncaught error'), `Playground ${i} on completes without error`); | ||
continue playgrounds; | ||
} | ||
} | ||
|
||
assert.ok(false, `Playground ${i} completes in under ${MAX_STEPS} steps`); | ||
} | ||
}); | ||
} | ||
}); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you say something about what
milestones
addresses that is not covered by the Ember built-ins explained here?https://guides.emberjs.com/release/testing/acceptance/
Maybe the reader can deduce from the Core docs, but I think it would help to be more explicit.
The official Ember docs imply, perhaps unintentionally, that the ember test helpers already provide all you need to test asynchronous code.
In a few Salsify projects we've used milestones heavily, but since it's not (yet) a widely used package, I've wondered how other teams tackle these problems. A mocking library like
sinon
? Simply not writing certain kinds of tests?A couple sentences, or maybe a before/after example as in the
ember-concurrency
docs, could clarify when it's the right tool.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great point. I had a bit written up about this (and it was also the bulk of the content in the mini-talk I gave at the Boston meetup last fall), but it got lost in the shuffle as I was writing everything else up.
I'll re-integrate that into the Ember page on the site (my goal is to keep these READMEs as lightweight as possible and push people toward the richer interactive site), and I'll also try and add a bit of more general-purpose motivation to the core docs as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Broadly speaking I think people tend to go one of three ways, depending on their personal priorities:
sleep
s throughout code to always ensure you've settled in the right state, but accept that your test suite will be slowI've done all of those at different points in the past, at Salsify and elsewhere, in codebases with and without Ember. My general experience is that projects tend to be in a constant state of flux between those different tradeoffs, depending on the values of the person who most recently 'fixed' the test suite.