Skip to content

Commit 6de19a6

Browse files
authored
Organise utilities (#59)
* Move registerServiceWorker, notification to utils - utils is a new directory under ./src/ holding misc modules that don't seem to fit anywhere else. - createStore was excluded from utils since the store is a core part of our react/redux app, and so it does make sense as a standalone module in the root ./src/ directory - Also fixed #37 unnecessary yield undefined. * Rename 'genericButton' -> 'controlButton' Also avoided variable name negation by renaming its argument 'notMinimal' -> 'minimal'. Refactor ReplControl to list declarations in decreasing levels of abstraction. (addresses review in PR #35) * Increase test coverage for workspace/Repl.tsx - Add a mockTypeError in mocks for situations that require a mock SourceError of some kind. * Abstract controlButton into components/commons Also fixed typescript type errors on yarn start. Resolves #36, resolves #37.
1 parent 5c0d34f commit 6de19a6

File tree

11 files changed

+190
-65
lines changed

11 files changed

+190
-65
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Button, IconName, Intent } from '@blueprintjs/core'
2+
import * as React from 'react'
3+
4+
export const controlButton = (
5+
label: string,
6+
icon: IconName,
7+
handleClick = () => {},
8+
intent = Intent.NONE,
9+
minimal = true
10+
) => (
11+
<Button
12+
onClick={handleClick}
13+
className={(minimal ? 'pt-minimal' : '') + ' col-xs-12'}
14+
intent={intent}
15+
icon={icon}
16+
>
17+
{label}
18+
</Button>
19+
)

src/components/commons/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './controlButton'

src/components/workspace/Editor.tsx

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import * as React from 'react'
2-
32
import AceEditor from 'react-ace'
43

5-
import { Button, IconName, Intent } from '@blueprintjs/core'
4+
import { controlButton } from '../commons'
65

76
import 'brace/mode/javascript'
87
import 'brace/theme/cobalt'
@@ -24,27 +23,11 @@ export interface IEditorProps {
2423

2524
class Editor extends React.Component<IEditorProps, {}> {
2625
public render() {
27-
const genericButton = (
28-
label: string,
29-
icon: IconName,
30-
handleClick = () => {},
31-
intent = Intent.NONE,
32-
notMinimal = false
33-
) => (
34-
<Button
35-
onClick={handleClick}
36-
className={(notMinimal ? '' : 'pt-minimal') + ' col-xs-12'}
37-
intent={intent}
38-
icon={icon}
39-
>
40-
{label}
41-
</Button>
42-
)
4326
const runButton = this.props.isRunning
4427
? null
45-
: genericButton('', 'play', this.props.handleEditorEval)
28+
: controlButton('', 'play', this.props.handleEditorEval)
4629
const stopButton = this.props.isRunning
47-
? genericButton('', 'stop', this.props.handleInterruptEval)
30+
? controlButton('', 'stop', this.props.handleInterruptEval)
4831
: null
4932
return (
5033
<div className="Editor">
Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import * as React from 'react'
22

3-
import { Button, IconName, Intent } from '@blueprintjs/core'
4-
53
import { sourceChapters } from '../../reducers/states'
4+
import { controlButton } from '../commons'
65

76
/**
87
* @property handleEvalEditor - A callback function for evaluation
@@ -14,48 +13,30 @@ export interface IReplControlProps {
1413
handleChapterSelect: (e: React.ChangeEvent<HTMLSelectElement>) => void
1514
}
1615

17-
const chapterSelect = (handleSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {}) => (
18-
<div className="pt-select pt-select">
19-
<select defaultValue={sourceChapters.slice(-1)[0].toString()} onChange={handleSelect}>
20-
{sourceChapters.map(chap => (
21-
<option key={chap} value={chap}>
22-
{`Source \xa7${chap}`}
23-
</option>
24-
))}
25-
</select>
26-
</div>
27-
)
28-
2916
class ReplControl extends React.Component<IReplControlProps, {}> {
3017
public render() {
31-
const genericButton = (
32-
label: string,
33-
icon: IconName,
34-
handleClick = () => {},
35-
intent = Intent.NONE,
36-
notMinimal = false
37-
) => (
38-
<Button
39-
onClick={handleClick}
40-
className={(notMinimal ? '' : 'pt-minimal') + ' col-xs-12'}
41-
intent={intent}
42-
icon={icon}
43-
>
44-
{label}
45-
</Button>
46-
)
47-
const evalButton = genericButton('', 'code', this.props.handleReplEval)
48-
const clearButton = genericButton('', 'remove', this.props.handleReplOutputClear)
4918
return (
5019
<div className="row end-xs">
5120
<div className="pt-control-group pt-fill">
5221
{chapterSelect(this.props.handleChapterSelect)}
53-
{evalButton}
54-
{clearButton}
22+
{controlButton('', 'code', this.props.handleReplEval)}
23+
{controlButton('', 'remove', this.props.handleReplOutputClear)}
5524
</div>
5625
</div>
5726
)
5827
}
5928
}
6029

30+
const chapterSelect = (handleSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {}) => (
31+
<div className="pt-select pt-select">
32+
<select defaultValue={sourceChapters.slice(-1)[0].toString()} onChange={handleSelect}>
33+
{sourceChapters.map(chap => (
34+
<option key={chap} value={chap}>
35+
{`Source \xa7${chap}`}
36+
</option>
37+
))}
38+
</select>
39+
</div>
40+
)
41+
6142
export default ReplControl
Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,41 @@
11
import { shallow } from 'enzyme'
22
import * as React from 'react'
33

4-
import { ResultOutput } from '../../../reducers/states'
4+
import { mockTypeError } from '../../../mocks/context'
5+
import {
6+
CodeOutput,
7+
ErrorOutput,
8+
InterpreterOutput,
9+
ResultOutput,
10+
RunningOutput
11+
} from '../../../reducers/states'
512
import Repl, { Output } from '../Repl'
613

14+
const mockRunningOutput: RunningOutput = {
15+
type: 'running',
16+
consoleLogs: ['a', 'bb', 'cccccccccccccccccccccccccccccccc', 'd']
17+
}
18+
19+
const mockCodeOutput: CodeOutput = {
20+
type: 'code',
21+
value: "display('');"
22+
}
23+
24+
const mockResultOutput: ResultOutput = {
25+
type: 'result',
26+
value: 42,
27+
consoleLogs: []
28+
}
29+
30+
const mockErrorOutput: ErrorOutput = {
31+
type: 'errors',
32+
errors: [mockTypeError()],
33+
consoleLogs: []
34+
}
35+
736
test('Repl renders correctly', () => {
837
const props = {
9-
output: [{ type: 'result', value: 'abc', consoleLogs: [] } as ResultOutput],
38+
output: [mockResultOutput, mockCodeOutput, mockErrorOutput, mockRunningOutput],
1039
replValue: '',
1140
handleReplValueChange: (newCode: string) => {},
1241
handleReplEval: () => {},
@@ -18,9 +47,52 @@ test('Repl renders correctly', () => {
1847
expect(tree.debug()).toMatchSnapshot()
1948
})
2049

21-
test("Output renders correctly for InterpreterOutput.type === 'result'", () => {
22-
const props: ResultOutput = { type: 'result', value: 'def', consoleLogs: [] }
50+
test('Code output renders correctly', () => {
51+
const app = <Output {...{ output: mockCodeOutput }} />
52+
const tree = shallow(app)
53+
expect(tree.debug()).toMatchSnapshot()
54+
})
55+
56+
test('Running output renders correctly', () => {
57+
const app = <Output {...{ output: mockRunningOutput }} />
58+
const tree = shallow(app)
59+
expect(tree.debug()).toMatchSnapshot()
60+
})
61+
62+
test('Result output (no consoleLogs) renders correctly', () => {
63+
const app = <Output {...{ output: mockResultOutput }} />
64+
const tree = shallow(app)
65+
expect(tree.debug()).toMatchSnapshot()
66+
})
67+
68+
test('Result output (with consoleLogs) renders correctly', () => {
69+
const props = {
70+
...mockResultOutput,
71+
consoleLogs: mockRunningOutput.consoleLogs
72+
}
73+
const app = <Output {...{ output: props }} />
74+
const tree = shallow(app)
75+
expect(tree.debug()).toMatchSnapshot()
76+
})
77+
78+
test('Error output (no consoleLogs) renders correctly', () => {
79+
const app = <Output {...{ output: mockErrorOutput }} />
80+
const tree = shallow(app)
81+
expect(tree.debug()).toMatchSnapshot()
82+
})
83+
84+
test('Error output (with consoleLogs) renders correctly', () => {
85+
const props = {
86+
...mockErrorOutput,
87+
consoleLogs: mockRunningOutput.consoleLogs
88+
}
2389
const app = <Output {...{ output: props }} />
2490
const tree = shallow(app)
2591
expect(tree.debug()).toMatchSnapshot()
2692
})
93+
94+
test('Empty output renders an empty card', () => {
95+
const app = <Output {...{ output: {} as InterpreterOutput }} />
96+
const tree = shallow(app)
97+
expect(tree.debug()).toMatchSnapshot()
98+
})

src/components/workspace/__tests__/__snapshots__/Repl.tsx.snap

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,38 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`Output renders correctly for InterpreterOutput.type === 'result' 1`] = `
3+
exports[`Code output renders correctly 1`] = `
44
"<Blueprint2.Card elevation={0} interactive={false}>
5-
<pre className=\\"resultOutput\\">
6-
&quot;def&quot;
5+
<pre className=\\"codeOutput\\">
6+
display(&#39;&#39;);
7+
</pre>
8+
</Blueprint2.Card>"
9+
`;
10+
11+
exports[`Empty output renders an empty card 1`] = `
12+
"<Blueprint2.Card elevation={0} interactive={false}>
13+
&#39;&#39;
14+
</Blueprint2.Card>"
15+
`;
16+
17+
exports[`Error output (no consoleLogs) renders correctly 1`] = `
18+
"<Blueprint2.Card elevation={0} interactive={false}>
19+
<pre className=\\"errorOutput\\">
20+
Line &lt;unknown&gt;: Expected , got .
21+
</pre>
22+
</Blueprint2.Card>"
23+
`;
24+
25+
exports[`Error output (with consoleLogs) renders correctly 1`] = `
26+
"<Blueprint2.Card elevation={0} interactive={false}>
27+
<pre className=\\"logOutput\\">
28+
a
29+
bb
30+
cccccccccccccccccccccccccccccccc
31+
d
32+
</pre>
33+
<br />
34+
<pre className=\\"errorOutput\\">
35+
Line &lt;unknown&gt;: Expected , got .
736
</pre>
837
</Blueprint2.Card>"
938
`;
@@ -14,6 +43,9 @@ exports[`Repl renders correctly 1`] = `
1443
<ReplControl output={{...}} replValue=\\"\\" handleReplValueChange={[Function: handleReplValueChange]} handleReplEval={[Function: handleReplEval]} handleReplOutputClear={[Function: handleReplOutputClear]} handleChapterSelect={[Function: handleChapterSelect]} />
1544
</div>
1645
<div className=\\"repl-output-parent\\">
46+
<Component output={{...}} />
47+
<Component output={{...}} />
48+
<Component output={{...}} />
1749
<Component output={{...}} />
1850
<div className=\\"repl-input-parent row pt-card pt-elevation-0\\">
1951
<ReplInput output={{...}} replValue=\\"\\" handleReplValueChange={[Function: handleReplValueChange]} handleReplEval={[Function: handleReplEval]} handleReplOutputClear={[Function: handleReplOutputClear]} handleChapterSelect={[Function: handleChapterSelect]} />
@@ -22,3 +54,36 @@ exports[`Repl renders correctly 1`] = `
2254
</div>
2355
</div>"
2456
`;
57+
58+
exports[`Result output (no consoleLogs) renders correctly 1`] = `
59+
"<Blueprint2.Card elevation={0} interactive={false}>
60+
<pre className=\\"resultOutput\\">
61+
42
62+
</pre>
63+
</Blueprint2.Card>"
64+
`;
65+
66+
exports[`Result output (with consoleLogs) renders correctly 1`] = `
67+
"<Blueprint2.Card elevation={0} interactive={false}>
68+
<pre className=\\"logOutput\\">
69+
a
70+
bb
71+
cccccccccccccccccccccccccccccccc
72+
d
73+
</pre>
74+
<pre className=\\"resultOutput\\">
75+
42
76+
</pre>
77+
</Blueprint2.Card>"
78+
`;
79+
80+
exports[`Running output renders correctly 1`] = `
81+
"<Blueprint2.Card elevation={0} interactive={false}>
82+
<pre className=\\"logOutput\\">
83+
a
84+
bb
85+
cccccccccccccccccccccccccccccccc
86+
d
87+
</pre>
88+
</Blueprint2.Card>"
89+
`;

src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Store } from 'redux'
99
import ApplicationContainer from './containers/ApplicationContainer'
1010
import createStore from './createStore'
1111
import { IState } from './reducers/states'
12-
import registerServiceWorker from './registerServiceWorker'
12+
import registerServiceWorker from './utils/registerServiceWorker'
1313

1414
import './styles/index.css'
1515

src/mocks/context.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { parse } from 'acorn'
12
import * as es from 'estree'
23

34
import { createContext } from '../slang'
45
import { Closure, Context, Frame } from '../slang/types'
6+
import { TypeError } from '../slang/utils/rttc'
57

68
export function mockContext(chapter = 1): Context {
79
return createContext(chapter)
@@ -31,3 +33,7 @@ export function mockRuntimeContext(): Context {
3133
export function mockClosure(): Closure {
3234
return new Closure({} as es.FunctionExpression, {} as Frame, {} as Context)
3335
}
36+
37+
export function mockTypeError(): TypeError {
38+
return new TypeError(parse(''), '', '', '')
39+
}

src/sagas/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Context, interrupt, runInContext } from '../slang'
66

77
import * as actions from '../actions'
88
import * as actionTypes from '../actions/actionTypes'
9-
import { showSuccessMessage, showWarningMessage } from '../notification'
9+
import { showSuccessMessage, showWarningMessage } from '../utils/notification'
1010

1111
function* evalCode(code: string, context: Context) {
1212
const { result, interrupted } = yield race({
@@ -53,8 +53,6 @@ function* interpreterSaga(): SagaIterator {
5353
yield put(actions.clearContext())
5454
yield put(actions.clearReplOutput())
5555
yield call(showSuccessMessage, `Switched to Source \xa7${newChapter}`)
56-
} else {
57-
yield undefined
5856
}
5957
})
6058
}
File renamed without changes.

0 commit comments

Comments
 (0)