Skip to content

Commit

Permalink
Initial state machine code
Browse files Browse the repository at this point in the history
  • Loading branch information
jbachhardie committed Jul 12, 2021
1 parent 458a663 commit c09a572
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 0 deletions.
11 changes: 11 additions & 0 deletions frontend/state-machine/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@frontend/state-machine",
"version": "1.0.0",
"main": "src/index.ts",
"types": "dist/index.d.ts",
"esbuild": "src/index.ts",
"license": "None",
"dependencies": {
"xstate": "^4.22.0"
}
}
2 changes: 2 additions & 0 deletions frontend/state-machine/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './page'
export * from './search'
37 changes: 37 additions & 0 deletions frontend/state-machine/src/page.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { interpret } from 'xstate'
import { pageMachine } from './page'
import { searchModel } from './search'

const fetchSearchResults = jest.fn()

const pageMachineWithMocks = pageMachine.withConfig({
services: {
fetchSearchResults,
},
})

it('when the user types in a search, loads that search', (done) => {
const mockData = {
searchUsers: {
resultsCount: 1,
results: [
{
textMatches: [],
user: {
id: 'test-user',
},
},
],
},
}
fetchSearchResults.mockResolvedValue(mockData)
const pageService = interpret(pageMachineWithMocks).onTransition((state) => {
if (state.matches('waiting') && state.context.searchResults) {
expect(state.context.searchResults).toMatchObject(mockData)
done()
}
})
pageService.start()
pageService.state.context.search.send(searchModel.events.updateInput('foo'))
pageService.state.context.search.send(searchModel.events.submit())
})
75 changes: 75 additions & 0 deletions frontend/state-machine/src/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ActorRefFrom, spawn, assign, send } from 'xstate'
import { createModel } from 'xstate/lib/model'
import { searchMachine, searchModel } from './search'

export type ErrorCode = 'RATE_LIMITED' | 'UNKNOWN_ERROR'

export const pageModel = createModel(
{
search: null as ActorRefFrom<typeof searchMachine>,
results: null,
pagination: null,
query: null as string | null,
page: 1,
searchResults: null,
errorCode: null as ErrorCode | null,
},
{
events: {
updateQuery: (value: string) => ({ value }),
},
}
)

export const pageMachine = pageModel.createMachine(
{
id: 'page',
context: pageModel.initialContext,
initial: 'waiting',
entry: pageModel.assign({
search: () => spawn(searchMachine, 'search'),
}),
states: {
waiting: {
on: {
updateQuery: {
target: 'loading',
actions: pageModel.assign({
query: (_, event) => event.value,
}),
},
},
},
loading: {
invoke: {
id: 'fetchSearchResults',
src: 'fetchSearchResults',
onDone: {
target: 'waiting',
actions: [
send(searchModel.events.loadingFinished(), {
to: (context) => context.search,
}),
assign({
searchResults: (_, event) => event.data,
}),
],
},
onError: {
target: 'waiting',
actions: assign({
errorCode: (_, event) => event.data,
}),
},
},
},
},
},
{
services: {
fetchSearchResults: async () => {
throw new Error('not implemented')
},
},
}
)
42 changes: 42 additions & 0 deletions frontend/state-machine/src/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { sendParent } from 'xstate'
import { createModel } from 'xstate/lib/model'
import { pageModel } from './page'

export const searchModel = createModel(
{
searchBoxInput: '',
},
{
events: {
updateInput: (value: string) => ({ value }),
submit: () => ({}),
loadingFinished: () => ({}),
},
}
)

export const searchMachine = searchModel.createMachine({
id: 'search',
context: searchModel.initialContext,
initial: 'waiting',
states: {
waiting: {
on: {
updateInput: {
actions: searchModel.assign({
searchBoxInput: (_, event) => event.value,
}),
},
submit: 'loading',
},
},
loading: {
entry: sendParent((context) =>
pageModel.events.updateQuery(context.searchBoxInput)
),
on: {
loadingFinished: 'waiting',
},
},
},
})
8 changes: 8 additions & 0 deletions frontend/state-machine/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.build.json",
"include": ["./src"],
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
}
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7077,6 +7077,11 @@ xss@^1.0.8:
commander "^2.20.3"
cssfilter "0.0.10"

xstate@^4.22.0:
version "4.22.0"
resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.22.0.tgz#5d6c2a762cb94c3170ee826d26194b7561d70aae"
integrity sha512-WBQS/XxmjCH6789fx5JXjct2pWA0ZI0a1Kx8PJMurzgytkJH3vC2+QganHWzK38vG9PyXHefyVG54UN5q6YVSw==

y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
Expand Down

0 comments on commit c09a572

Please sign in to comment.