You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1029 line
25 KiB

  1. /* eslint-env mocha */
  2. import React, { PureComponent } from "react"
  3. import expect from "expect"
  4. import System from "core/system"
  5. import { fromJS } from "immutable"
  6. import { render } from "enzyme"
  7. import ViewPlugin from "core/plugins/view/index.js"
  8. import filterPlugin from "core/plugins/filter/index.js"
  9. import { connect, Provider } from "react-redux"
  10. describe("bound system", function(){
  11. describe("wrapActions", function(){
  12. it("should replace an action", function(){
  13. // Given
  14. const system = new System({
  15. plugins: {
  16. statePlugins: {
  17. josh: {
  18. actions: {
  19. simple: () => {
  20. return { type: "simple" }
  21. }
  22. },
  23. wrapActions: {
  24. simple: () => () => {
  25. return { type: "newSimple" }
  26. }
  27. }
  28. }
  29. }
  30. }
  31. })
  32. // When
  33. let action = system.getSystem().joshActions.simple(1)
  34. expect(action).toEqual({
  35. type: "newSimple"
  36. })
  37. })
  38. it("should expose the original action, and the system as args", function(){
  39. // Given
  40. const simple = () => ({type: "simple" })
  41. const system = new System({
  42. plugins: {
  43. statePlugins: {
  44. josh: {
  45. actions: { simple },
  46. wrapActions: {
  47. simple: (oriAction, system) => (actionArg) => {
  48. return {
  49. type: "newSimple",
  50. oriActionResult: oriAction(),
  51. system: system.getSystem(),
  52. actionArg
  53. }
  54. }
  55. }
  56. }
  57. }
  58. }
  59. })
  60. // When
  61. let action = system.getSystem().joshActions.simple(1)
  62. expect(action).toEqual({
  63. type: "newSimple",
  64. oriActionResult: { type: "simple" },
  65. system: system.getSystem(),
  66. actionArg: 1
  67. })
  68. })
  69. it("should support multiple wraps of the same action", function(){
  70. const system = new System({
  71. plugins: [
  72. {
  73. statePlugins: {
  74. kyle: {
  75. actions: {
  76. simple: () => {
  77. return {
  78. type: "simple",
  79. }
  80. }
  81. }
  82. }
  83. }
  84. },
  85. {
  86. statePlugins: {
  87. kyle: {
  88. wrapActions: {
  89. simple: (ori) => () => {
  90. return {
  91. ...ori(),
  92. firstWrap: true
  93. }
  94. }
  95. }
  96. }
  97. }
  98. },
  99. {
  100. statePlugins: {
  101. kyle: {
  102. wrapActions: {
  103. simple: (ori) => () => {
  104. return {
  105. ...ori(),
  106. secondWrap: true
  107. }
  108. }
  109. }
  110. }
  111. }
  112. }
  113. ]
  114. })
  115. // When
  116. let action = system.getSystem().kyleActions.simple(1)
  117. expect(action).toEqual({
  118. type: "simple",
  119. firstWrap: true,
  120. secondWrap: true,
  121. })
  122. })
  123. it("should execute wrapActions in the order they appear ( via plugins )", function(){
  124. const system = new System({
  125. plugins: [
  126. {
  127. statePlugins: {
  128. kyle: {
  129. actions: {
  130. simple: () => {
  131. return {
  132. type: "one",
  133. }
  134. }
  135. }
  136. }
  137. }
  138. },
  139. {
  140. statePlugins: {
  141. kyle: {
  142. wrapActions: {
  143. simple: (ori) => () => {
  144. const obj = ori()
  145. obj.type += "-two"
  146. return obj
  147. }
  148. }
  149. }
  150. }
  151. },
  152. {
  153. statePlugins: {
  154. kyle: {
  155. wrapActions: {
  156. simple: (ori) => () => {
  157. const obj = ori()
  158. obj.type += "-three"
  159. return obj
  160. }
  161. }
  162. }
  163. }
  164. }
  165. ]
  166. })
  167. // When
  168. let action = system.getSystem().kyleActions.simple(1)
  169. expect(action.type).toEqual("one-two-three")
  170. })
  171. it("should have a the latest system", function(){
  172. // Given
  173. const system = new System({
  174. plugins: [
  175. {
  176. statePlugins: {
  177. kyle: {
  178. actions: {
  179. simple: () => {
  180. return {
  181. type: "one",
  182. }
  183. }
  184. },
  185. wrapActions: {
  186. simple: (ori, {joshActions}) => () => {
  187. return joshActions.hello()
  188. }
  189. }
  190. }
  191. }
  192. },
  193. ]
  194. })
  195. // When
  196. const kyleActions = system.getSystem().kyleActions
  197. system.register({
  198. statePlugins: {
  199. josh: {
  200. actions: {
  201. hello(){ return {type: "hello" } }
  202. }
  203. }
  204. }
  205. })
  206. const action = kyleActions.simple()
  207. expect(action).toEqual({ type: "hello"})
  208. })
  209. it.skip("should be able to create async actions", function(){
  210. const system = new System({
  211. plugins: [
  212. {
  213. statePlugins: {
  214. kyle: {
  215. actions: {
  216. simple: () => {
  217. return {
  218. type: "one",
  219. }
  220. }
  221. }
  222. }
  223. }
  224. },
  225. {
  226. statePlugins: {
  227. kyle: {
  228. wrapActions: {
  229. // eslint-disable-next-line no-unused-vars
  230. simple: (ori) => (arg) => (sys) => {
  231. return { type: "called" }
  232. }
  233. }
  234. }
  235. }
  236. },
  237. ]
  238. })
  239. // When
  240. let action = system.getSystem().kyleActions.simple(1)
  241. expect(action.type).toEqual("called")
  242. })
  243. })
  244. describe("fn", function() {
  245. it("should return helper functions", function () {
  246. // Given
  247. const system = new System({
  248. plugins: [
  249. filterPlugin
  250. ]
  251. })
  252. // When
  253. const fn = system.getSystem().fn.opsFilter
  254. expect(typeof fn).toEqual("function")
  255. })
  256. })
  257. describe("selectors", function(){
  258. it("should have the first arg be the nested state, and all other args to follow", function(){
  259. // Given
  260. const system = new System({
  261. state: {
  262. josh: {
  263. one: 1
  264. }
  265. },
  266. plugins: {
  267. statePlugins: {
  268. josh: {
  269. selectors: {
  270. simple: (state, arg1) => {
  271. return { state, arg1 }
  272. }
  273. }
  274. }
  275. }
  276. }
  277. })
  278. // When
  279. let res = system.getSystem().joshSelectors.simple(1)
  280. expect(res).toEqual({
  281. state: fromJS({
  282. one: 1
  283. }),
  284. arg1: 1
  285. })
  286. })
  287. describe("when selector returns a function", function(){
  288. it("should pass the system to that function", function(){
  289. // Given
  290. const system = new System({
  291. plugins: {
  292. statePlugins: {
  293. josh: {
  294. selectors: {
  295. advanced: () => (mySystem) => {
  296. // Then
  297. expect(mySystem).toEqual(system.getSystem())
  298. return "hi"
  299. }
  300. }
  301. }
  302. }
  303. }
  304. })
  305. // When
  306. let res = system.getSystem().joshSelectors.advanced(1)
  307. expect(res).toEqual("hi")
  308. })
  309. })
  310. describe("wrapSelectors", () => {
  311. it("should wrap a selector and provide a reference to the original", function(){
  312. // Given
  313. const system = new System({
  314. plugins: [
  315. {
  316. statePlugins: {
  317. doge: {
  318. selectors: {
  319. wow: () => (system) => {
  320. return "original"
  321. }
  322. }
  323. }
  324. }
  325. },
  326. {
  327. statePlugins: {
  328. doge: {
  329. wrapSelectors: {
  330. wow: (ori) => (system) => {
  331. // Then
  332. return ori() + " wrapper"
  333. }
  334. }
  335. }
  336. }
  337. }
  338. ]
  339. })
  340. // When
  341. let res = system.getSystem().dogeSelectors.wow(1)
  342. expect(res).toEqual("original wrapper")
  343. })
  344. it("should provide a live reference to the system to a wrapper", function(done){
  345. // Given
  346. const mySystem = new System({
  347. plugins: [
  348. {
  349. statePlugins: {
  350. doge: {
  351. selectors: {
  352. wow: () => (system) => {
  353. return "original"
  354. }
  355. }
  356. }
  357. }
  358. },
  359. {
  360. statePlugins: {
  361. doge: {
  362. wrapSelectors: {
  363. wow: (ori, system) => () => {
  364. // Then
  365. expect(mySystem.getSystem()).toEqual(system.getSystem())
  366. done()
  367. return ori() + " wrapper"
  368. }
  369. }
  370. }
  371. }
  372. }
  373. ]
  374. })
  375. mySystem.getSystem().dogeSelectors.wow(1)
  376. })
  377. it("should provide the state as the first argument to the inner function", function(done){
  378. // Given
  379. const mySystem = new System({
  380. state: {
  381. doge: {
  382. abc: "123"
  383. }
  384. },
  385. plugins: [
  386. {
  387. statePlugins: {
  388. doge: {
  389. selectors: {
  390. wow: () => (system) => {
  391. return "original"
  392. }
  393. }
  394. }
  395. }
  396. },
  397. {
  398. statePlugins: {
  399. doge: {
  400. wrapSelectors: {
  401. wow: (ori, system) => (dogeState) => {
  402. // Then
  403. expect(dogeState.toJS().abc).toEqual("123")
  404. done()
  405. return ori() + " wrapper"
  406. }
  407. }
  408. }
  409. }
  410. }
  411. ]
  412. })
  413. mySystem.getSystem().dogeSelectors.wow(1)
  414. })
  415. })
  416. })
  417. describe("getComponent", function() {
  418. it("returns a component from the system", function() {
  419. const system = new System({
  420. plugins: [
  421. ViewPlugin,
  422. {
  423. components: {
  424. test: ({ name }) => <div>{name} component</div>
  425. }
  426. }
  427. ]
  428. })
  429. // When
  430. let Component = system.getSystem().getComponent("test")
  431. const renderedComponent = render(<Component name="Test" />)
  432. expect(renderedComponent.text()).toEqual("Test component")
  433. })
  434. it("allows container components to provide their own `mapStateToProps` function", function() {
  435. // Given
  436. class ContainerComponent extends PureComponent {
  437. mapStateToProps(nextState, props) {
  438. return {
  439. "fromMapState": "This came from mapStateToProps"
  440. }
  441. }
  442. static defaultProps = {
  443. "fromMapState" : ""
  444. }
  445. render() {
  446. const { exampleSelectors, fromMapState, fromOwnProps } = this.props
  447. return (
  448. <div>{ fromMapState } {exampleSelectors.foo()} {fromOwnProps}</div>
  449. )
  450. }
  451. }
  452. const system = new System({
  453. plugins: [
  454. ViewPlugin,
  455. {
  456. components: {
  457. ContainerComponent
  458. }
  459. },
  460. {
  461. statePlugins: {
  462. example: {
  463. selectors: {
  464. foo() { return "and this came from the system" }
  465. }
  466. }
  467. }
  468. }
  469. ]
  470. })
  471. // When
  472. let Component = system.getSystem().getComponent("ContainerComponent", true)
  473. const renderedComponent = render(
  474. <Provider store={system.getStore()}>
  475. <Component fromOwnProps="and this came from my own props" />
  476. </Provider>
  477. )
  478. // Then
  479. expect(renderedComponent.text()).toEqual("This came from mapStateToProps and this came from the system and this came from my own props")
  480. })
  481. it("gives the system and own props as props to a container's `mapStateToProps` function", function() {
  482. // Given
  483. class ContainerComponent extends PureComponent {
  484. mapStateToProps(nextState, props) {
  485. const { exampleSelectors, fromMapState, fromOwnProps } = props
  486. return {
  487. "fromMapState": `This came from mapStateToProps ${exampleSelectors.foo()} ${fromOwnProps}`
  488. }
  489. }
  490. static defaultProps = {
  491. "fromMapState" : ""
  492. }
  493. render() {
  494. const { fromMapState } = this.props
  495. return (
  496. <div>{ fromMapState }</div>
  497. )
  498. }
  499. }
  500. const system = new System({
  501. plugins: [
  502. ViewPlugin,
  503. {
  504. components: {
  505. ContainerComponent
  506. }
  507. },
  508. {
  509. statePlugins: {
  510. example: {
  511. selectors: {
  512. foo() { return "and this came from the system" }
  513. }
  514. }
  515. }
  516. }
  517. ]
  518. })
  519. // When
  520. let Component = system.getSystem().getComponent("ContainerComponent", true)
  521. const renderedComponent = render(
  522. <Provider store={system.getStore()}>
  523. <Component fromOwnProps="and this came from my own props" />
  524. </Provider>
  525. )
  526. // Then
  527. expect(renderedComponent.text()).toEqual("This came from mapStateToProps and this came from the system and this came from my own props")
  528. })
  529. })
  530. describe("afterLoad", function() {
  531. it("should call a plugin's `afterLoad` method after the plugin is loaded", function() {
  532. // Given
  533. const system = new System({
  534. plugins: [
  535. {
  536. afterLoad(system) {
  537. this.rootInjects.wow = system.dogeSelectors.wow
  538. },
  539. statePlugins: {
  540. doge: {
  541. selectors: {
  542. wow: () => (system) => {
  543. return "so selective"
  544. }
  545. }
  546. }
  547. }
  548. }
  549. ]
  550. })
  551. // When
  552. let res = system.getSystem().wow()
  553. expect(res).toEqual("so selective")
  554. })
  555. it("should call a preset plugin's `afterLoad` method after the plugin is loaded", function() {
  556. // Given
  557. const MyPlugin = {
  558. afterLoad(system) {
  559. this.rootInjects.wow = system.dogeSelectors.wow
  560. },
  561. statePlugins: {
  562. doge: {
  563. selectors: {
  564. wow: () => (system) => {
  565. return "so selective"
  566. }
  567. }
  568. }
  569. }
  570. }
  571. const system = new System({
  572. plugins: [
  573. [MyPlugin]
  574. ]
  575. })
  576. // When
  577. let res = system.getSystem().wow()
  578. expect(res).toEqual("so selective")
  579. })
  580. it("should call a function preset plugin's `afterLoad` method after the plugin is loaded", function() {
  581. // Given
  582. const MyPlugin = {
  583. afterLoad(system) {
  584. this.rootInjects.wow = system.dogeSelectors.wow
  585. },
  586. statePlugins: {
  587. doge: {
  588. selectors: {
  589. wow: () => (system) => {
  590. return "so selective"
  591. }
  592. }
  593. }
  594. }
  595. }
  596. const system = new System({
  597. plugins: [
  598. () => {
  599. return [MyPlugin]
  600. }
  601. ]
  602. })
  603. // When
  604. let res = system.getSystem().wow()
  605. expect(res).toEqual("so selective")
  606. })
  607. it("should call a registered plugin's `afterLoad` method after the plugin is loaded", function() {
  608. // Given
  609. const MyPlugin = {
  610. afterLoad(system) {
  611. this.rootInjects.wow = system.dogeSelectors.wow
  612. },
  613. statePlugins: {
  614. doge: {
  615. selectors: {
  616. wow: () => (system) => {
  617. return "so selective"
  618. }
  619. }
  620. }
  621. }
  622. }
  623. const system = new System({
  624. plugins: []
  625. })
  626. system.register([MyPlugin])
  627. // When
  628. let res = system.getSystem().wow()
  629. expect(res).toEqual("so selective")
  630. })
  631. })
  632. describe("rootInjects", function() {
  633. it("should attach a rootInject function as an instance method", function() {
  634. // This is the same thing as the `afterLoad` tests, but is here for posterity
  635. // Given
  636. const system = new System({
  637. plugins: [
  638. {
  639. afterLoad(system) {
  640. this.rootInjects.wow = system.dogeSelectors.wow
  641. },
  642. statePlugins: {
  643. doge: {
  644. selectors: {
  645. wow: () => (system) => {
  646. return "so selective"
  647. }
  648. }
  649. }
  650. }
  651. }
  652. ]
  653. })
  654. // When
  655. let res = system.getSystem().wow()
  656. expect(res).toEqual("so selective")
  657. })
  658. })
  659. describe("error catching", function() {
  660. it("should encapsulate thrown errors in an afterLoad method", function() {
  661. // Given
  662. const ThrowyPlugin = {
  663. afterLoad(system) {
  664. throw new Error("afterLoad BREAKS STUFF!")
  665. },
  666. statePlugins: {
  667. doge: {
  668. selectors: {
  669. wow: () => (system) => {
  670. return "so selective"
  671. }
  672. }
  673. }
  674. }
  675. }
  676. const system = new System({
  677. plugins: []
  678. })
  679. // When
  680. expect(function() {
  681. system.register([ThrowyPlugin])
  682. // let resSystem = system.getSystem()
  683. }).toNotThrow()
  684. })
  685. it("should encapsulate thrown errors in an action creator", function(){
  686. // Given
  687. const system = new System({
  688. plugins: {
  689. statePlugins: {
  690. throw: {
  691. actions: {
  692. func() {
  693. throw new Error("this action creator THROWS!")
  694. }
  695. }
  696. }
  697. }
  698. }
  699. })
  700. expect(function() {
  701. // TODO: fix existing action error catcher that creates THROWN ERR actions
  702. system.getSystem().throwActions.func()
  703. }).toNotThrow()
  704. })
  705. it("should encapsulate thrown errors in a reducer", function(){
  706. // Given
  707. const system = new System({
  708. plugins: {
  709. statePlugins: {
  710. throw: {
  711. actions: {
  712. func: () => {
  713. return {
  714. type: "THROW_FUNC",
  715. payload: "BOOM!"
  716. }
  717. }
  718. },
  719. reducers: {
  720. "THROW_FUNC": (state, action) => {
  721. throw new Error("this reducer EXPLODES!")
  722. }
  723. }
  724. }
  725. }
  726. }
  727. })
  728. expect(function() {
  729. system.getSystem().throwActions.func()
  730. }).toNotThrow()
  731. })
  732. it("should encapsulate thrown errors in a selector", function(){
  733. // Given
  734. const system = new System({
  735. plugins: {
  736. statePlugins: {
  737. throw: {
  738. selectors: {
  739. func: (state, arg1) => {
  740. throw new Error("this selector THROWS!")
  741. }
  742. }
  743. }
  744. }
  745. }
  746. })
  747. expect(system.getSystem().throwSelectors.func).toNotThrow()
  748. })
  749. it("should encapsulate thrown errors in a complex selector", function(){
  750. // Given
  751. const system = new System({
  752. plugins: {
  753. statePlugins: {
  754. throw: {
  755. selectors: {
  756. func: (state, arg1) => system => {
  757. throw new Error("this selector THROWS!")
  758. }
  759. }
  760. }
  761. }
  762. }
  763. })
  764. expect(system.getSystem().throwSelectors.func).toNotThrow()
  765. })
  766. it("should encapsulate thrown errors in a wrapAction", function(){
  767. // Given
  768. const system = new System({
  769. plugins: {
  770. statePlugins: {
  771. throw: {
  772. actions: {
  773. func: () => {
  774. return {
  775. type: "THROW_FUNC",
  776. payload: "this original action does NOT throw"
  777. }
  778. }
  779. },
  780. wrapActions: {
  781. func: (ori) => (...args) => {
  782. throw new Error("this wrapAction UNRAVELS EVERYTHING!")
  783. }
  784. }
  785. }
  786. }
  787. }
  788. })
  789. expect(system.getSystem().throwActions.func).toNotThrow()
  790. })
  791. it("should encapsulate thrown errors in a wrapSelector", function(){
  792. // Given
  793. const system = new System({
  794. plugins: {
  795. statePlugins: {
  796. throw: {
  797. selectors: {
  798. func: (state, arg1) => {
  799. return 123
  800. }
  801. },
  802. wrapSelectors: {
  803. func: (ori) => (...props) => {
  804. return ori(...props)
  805. }
  806. }
  807. }
  808. }
  809. }
  810. })
  811. expect(system.getSystem().throwSelectors.func).toNotThrow()
  812. })
  813. describe("components", function() {
  814. it("should catch errors thrown inside of React Component Class render methods", function() {
  815. // Given
  816. class BrokenComponent extends React.Component {
  817. // eslint-disable-next-line react/require-render-return
  818. render() {
  819. throw new Error("This component is broken")
  820. }
  821. }
  822. const system = new System({
  823. plugins: [
  824. ViewPlugin,
  825. {
  826. components: {
  827. BrokenComponent
  828. }
  829. }
  830. ]
  831. })
  832. // When
  833. let Component = system.getSystem().getComponent("BrokenComponent")
  834. const renderedComponent = render(<Component />)
  835. // Then
  836. expect(renderedComponent.text()).toEqual("😱 Could not render BrokenComponent, see the console.")
  837. })
  838. it("should catch errors thrown inside of pure component render methods", function() {
  839. // Given
  840. class BrokenComponent extends PureComponent {
  841. // eslint-disable-next-line react/require-render-return
  842. render() {
  843. throw new Error("This component is broken")
  844. }
  845. }
  846. const system = new System({
  847. plugins: [
  848. ViewPlugin,
  849. {
  850. components: {
  851. BrokenComponent
  852. }
  853. }
  854. ]
  855. })
  856. // When
  857. let Component = system.getSystem().getComponent("BrokenComponent")
  858. const renderedComponent = render(<Component />)
  859. // Then
  860. expect(renderedComponent.text()).toEqual("😱 Could not render BrokenComponent, see the console.")
  861. })
  862. it("should catch errors thrown inside of stateless component functions", function() {
  863. // Given
  864. // eslint-disable-next-line react/require-render-return
  865. let BrokenComponent = function BrokenComponent() { throw new Error("This component is broken") }
  866. const system = new System({
  867. plugins: [
  868. ViewPlugin,
  869. {
  870. components: {
  871. BrokenComponent
  872. }
  873. }
  874. ]
  875. })
  876. // When
  877. let Component = system.getSystem().getComponent("BrokenComponent")
  878. const renderedComponent = render(<Component />)
  879. // Then
  880. expect(renderedComponent.text().startsWith("😱 Could not render")).toEqual(true)
  881. })
  882. it("should catch errors thrown inside of container components", function() {
  883. // Given
  884. class BrokenComponent extends React.Component {
  885. // eslint-disable-next-line react/require-render-return
  886. render() {
  887. throw new Error("This component is broken")
  888. }
  889. }
  890. const system = new System({
  891. plugins: [
  892. ViewPlugin,
  893. {
  894. components: {
  895. BrokenComponent
  896. }
  897. }
  898. ]
  899. })
  900. // When
  901. let Component = system.getSystem().getComponent("BrokenComponent", true)
  902. const renderedComponent = render(
  903. <Provider store={system.getStore()}>
  904. <Component />
  905. </Provider>
  906. )
  907. // Then
  908. expect(renderedComponent.text()).toEqual("😱 Could not render BrokenComponent, see the console.")
  909. })
  910. })
  911. })
  912. })