Skip to content

Commit 6e84c8b

Browse files
Added showcase counter example and tests
1 parent 814f1cd commit 6e84c8b

File tree

17 files changed

+15530
-11705
lines changed

17 files changed

+15530
-11705
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,13 @@ resolvers += "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositor
3434
### How to use it
3535

3636
* Demo Application
37-
* [app](showcase/src/main/scala/scommons/react/showcase/app/ShowcaseReactApp.scala) => [tests](showcase/src/test/scala/scommons/react/showcase/app/ShowcaseReactAppSpec.scala)
38-
* [live](https://scommons.org/scommons-react/showcase/) => [How to Build and Run](showcase/README.md)
37+
* [live demo](https://scommons.org/scommons-react/showcase/) => [How to Build and Run](showcase/README.md)
38+
* [AppMain](showcase/src/main/scala/scommons/react/showcase/app/ShowcaseReactApp.scala) => [tests](showcase/src/test/scala/scommons/react/showcase/app/ShowcaseReactAppSpec.scala)
39+
* [AppState](showcase/src/main/scala/scommons/react/showcase/app/ShowcaseState.scala) => [tests](showcase/src/test/scala/scommons/react/showcase/app/ShowcaseStateReducerSpec.scala)
40+
* [CounterActions](showcase/src/main/scala/scommons/react/showcase/app/counter/CounterActions.scala) => [tests](showcase/src/test/scala/scommons/react/showcase/app/counter/CounterActionsSpec.scala)
41+
* [CounterController](showcase/src/main/scala/scommons/react/showcase/app/counter/CounterController.scala) => [tests](showcase/src/test/scala/scommons/react/showcase/app/counter/CounterControllerSpec.scala)
42+
* [CounterPanel](showcase/src/main/scala/scommons/react/showcase/app/counter/CounterPanel.scala) => [tests](showcase/src/test/scala/scommons/react/showcase/app/counter/CounterPanelSpec.scala)
43+
* [CounterState](showcase/src/main/scala/scommons/react/showcase/app/counter/CounterState.scala) => [tests](showcase/src/test/scala/scommons/react/showcase/app/counter/CounterStateReducerSpec.scala)
3944

4045
* Components:
4146
* [ClassComponent](showcase/src/main/scala/scommons/react/showcase/ClassComponentDemo.scala) => [tests](showcase/src/test/scala/scommons/react/showcase/ClassComponentDemoSpec.scala)

docs/showcase/assets/scommons-react-showcase-opt-library.js

Lines changed: 14631 additions & 11370 deletions
Large diffs are not rendered by default.

docs/showcase/assets/scommons-react-showcase-opt.js

Lines changed: 513 additions & 328 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

project/src/main/scala/definitions/ReactShowcase.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ object ReactShowcase extends ScalaJsModule {
3939
override val internalDependencies: Seq[ClasspathDep[ProjectReference]] = Seq(
4040
ReactCore.definition,
4141
ReactDom.definition,
42+
ReactRedux.definition,
4243
ReactTest.definition % "test",
4344
ReactTestDom.definition % "test"
4445
)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package scommons.react.redux.task
2+
3+
import org.scalamock.scalatest.AsyncMockFactory
4+
import org.scalatest.{AsyncFlatSpec, Matchers, Succeeded}
5+
6+
import scala.concurrent.{ExecutionContext, Future}
7+
import scala.scalajs.concurrent.JSExecutionContext
8+
import scala.util.{Success, Try}
9+
10+
class FutureTaskSpec extends AsyncFlatSpec
11+
with Matchers
12+
with AsyncMockFactory {
13+
14+
implicit override val executionContext: ExecutionContext = JSExecutionContext.queue
15+
16+
it should "call future.onComplete when onComplete" in {
17+
//given
18+
val future = Future.successful(())
19+
val task = FutureTask("test message", future)
20+
val f = mockFunction[Try[_], Unit]
21+
22+
//then
23+
f.expects(Success(()))
24+
25+
//when
26+
task.onComplete(f)
27+
28+
future.map(_ => Succeeded)
29+
}
30+
}

showcase/src/main/scala/scommons/react/showcase/app/ShowcaseReactApp.scala

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package scommons.react.showcase.app
22

33
import io.github.shogowada.scalajs.reactjs.ReactDOM
4+
import io.github.shogowada.scalajs.reactjs.redux.ReactRedux._
5+
import io.github.shogowada.scalajs.reactjs.redux.Redux
46
import org.scalajs.dom.document
57
import scommons.react._
68
import scommons.react.showcase.ErrorBoundaryDemo
9+
import scommons.react.showcase.app.counter.{CounterActions, CounterController}
710

811
object ShowcaseReactApp {
912

@@ -12,10 +15,15 @@ object ShowcaseReactApp {
1215

1316
document.title = "scommons-react-showcase"
1417

18+
val store = Redux.createStore(ShowcaseStateReducer.reduce)
19+
20+
val counterActions = new CounterActions
21+
val counterController = new CounterController(counterActions)
22+
1523
ReactDOM.render(
16-
<(ErrorBoundaryDemo())()(
17-
<.p()(
18-
"Hello World!"
24+
<.Provider(^.store := store)(
25+
<(ErrorBoundaryDemo())()(
26+
<(counterController()).empty
1927
)
2028
),
2129
mountNode
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package scommons.react.showcase.app
2+
3+
import scommons.react.redux.task.{AbstractTask, TaskReducer}
4+
import scommons.react.showcase.app.counter.{CounterState, CounterStateReducer}
5+
6+
trait ShowcaseStateDef {
7+
8+
def currentTask: Option[AbstractTask]
9+
def counterState: CounterState
10+
}
11+
12+
case class ShowcaseState(currentTask: Option[AbstractTask],
13+
counterState: CounterState) extends ShowcaseStateDef
14+
15+
object ShowcaseStateReducer {
16+
17+
def reduce(state: Option[ShowcaseState], action: Any): ShowcaseState = ShowcaseState(
18+
currentTask = TaskReducer(state.flatMap(_.currentTask), action),
19+
counterState = CounterStateReducer(state.map(_.counterState), action)
20+
)
21+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package scommons.react.showcase.app.counter
2+
3+
import io.github.shogowada.scalajs.reactjs.redux.Action
4+
import io.github.shogowada.scalajs.reactjs.redux.Redux.Dispatch
5+
import scommons.react.redux.task.{FutureTask, TaskAction}
6+
import scommons.react.showcase.app.counter.CounterActions._
7+
8+
import scala.concurrent.ExecutionContext.Implicits.global
9+
import scala.concurrent.Future
10+
import scala.util.Success
11+
12+
class CounterActions {
13+
14+
def changeCounter(dispatch: Dispatch, counter: Int, dx: Int): CounterChangeAction = {
15+
16+
// in real App may call some API here
17+
val future = Future.successful(counter + dx).andThen {
18+
case Success(result) => dispatch(CounterChangedAction(result))
19+
20+
// Errors should be handled generally by the UI Task Manager
21+
//case Failure(e) => onError(())(e)
22+
}
23+
24+
CounterChangeAction(FutureTask("Changing Counter", future))
25+
}
26+
}
27+
28+
object CounterActions {
29+
30+
/** Asynchronous, potentially calling some API, actions should extend [[TaskAction]],
31+
* so the UI Task Manager will be able to display status/loading
32+
*/
33+
case class CounterChangeAction(task: FutureTask[Int]) extends TaskAction
34+
35+
/** Simple actions just extend redux Action
36+
*/
37+
case class CounterChangedAction(counter: Int) extends Action
38+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package scommons.react.showcase.app.counter
2+
3+
import io.github.shogowada.scalajs.reactjs.React.Props
4+
import io.github.shogowada.scalajs.reactjs.redux.Redux.Dispatch
5+
import scommons.react.UiComponent
6+
import scommons.react.redux.BaseStateController
7+
import scommons.react.showcase.app.ShowcaseStateDef
8+
9+
class CounterController(actions: CounterActions)
10+
extends BaseStateController[ShowcaseStateDef, CounterPanelProps] {
11+
12+
lazy val uiComponent: UiComponent[CounterPanelProps] = CounterPanel
13+
14+
def mapStateToProps(dispatch: Dispatch, state: ShowcaseStateDef, props: Props[Unit]): CounterPanelProps = {
15+
CounterPanelProps(dispatch, actions, state.counterState)
16+
}
17+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package scommons.react.showcase.app.counter
2+
3+
import io.github.shogowada.scalajs.reactjs.events.MouseSyntheticEvent
4+
import io.github.shogowada.scalajs.reactjs.redux.Redux.Dispatch
5+
import scommons.react._
6+
7+
case class CounterPanelProps(dispatch: Dispatch,
8+
actions: CounterActions,
9+
state: CounterState)
10+
11+
object CounterPanel extends FunctionComponent[CounterPanelProps] {
12+
13+
protected def render(compProps: Props): ReactElement = {
14+
val props = compProps.wrapped
15+
16+
<.>()(
17+
<.p()(
18+
"Welcome to the React Counter showcase example App." +
19+
" Use buttons bellow to increase/decrease the counter:"
20+
),
21+
22+
<.p()(s"${props.state.value}"),
23+
24+
<.button(^.onClick := { _: MouseSyntheticEvent =>
25+
props.dispatch(props.actions.changeCounter(props.dispatch, props.state.value, 1))
26+
})("+"),
27+
28+
<.button(^.onClick := { _: MouseSyntheticEvent =>
29+
props.dispatch(props.actions.changeCounter(props.dispatch, props.state.value, -1))
30+
})("-")
31+
)
32+
}
33+
}

0 commit comments

Comments
 (0)