From 764a299e7e7e5f0643523d1aa691fcb7a87d4f99 Mon Sep 17 00:00:00 2001 From: Yassa Tamer <58339464+Yassa-hue@users.noreply.github.com> Date: Sat, 18 Nov 2023 01:31:20 +0200 Subject: [PATCH] Add rescript example (#442) (#552) * Add rescript example (#442) * Add rescript build configurations * Create rescript page components * Add rescript deps * Solve render comments issue * Refactor css transaction components React Hooks shouldn't be used in loops or if statement. * Ignore the rescript output js files from lint The rescript output js files from lint because it doesn't follow the lint rules. * Apply review requested changes * Remove unused tag * Add ROR binding * Apply review requested changes * remove action js file * Remove .mjs files * Compile rescript files before running js tests * Build recript files before test * minor change * remove react-on-rails binding * migrate to tailwind * remove bs.mjs files * remove unused scss files * fix rescript spec tests * build rescript before test * refactor rescript src * fix add commit fail bug * refactor the types * format rescript files * remove the types files * update error rescript page state design * refactor switch statements * move store comment state to the form component * format rescript * rename the saving state * empty commit --- .eslintignore | 1 + .github/workflows/rspec_test.yml | 3 + .gitignore | 5 + Procfile.dev | 1 + app/controllers/pages_controller.rb | 2 + app/views/pages/rescript.html.erb | 1 + bsconfig.json | 28 ++++ .../NavigationBar/NavigationBar.jsx | 8 + .../app/bundles/comments/constants/paths.js | 1 + .../comments/rescript/Actions/Actions.res | 59 +++++++ .../rescript/CommentForm/CommentForm.res | 149 ++++++++++++++++++ .../CommentForm/forms/HorizontalForm.res | 37 +++++ .../rescript/CommentForm/forms/InlineForm.res | 40 +++++ .../CommentForm/forms/StackedFrom.res | 37 +++++ .../CommentList/AlertError/AlertError.res | 26 +++ .../rescript/CommentList/Comment/Comment.res | 19 +++ .../rescript/CommentList/CommentList.res | 23 +++ .../comments/rescript/Header/Header.res | 55 +++++++ .../rescript/ReScriptShow.module.scss | 20 +++ .../comments/rescript/ReScriptShow.res | 66 ++++++++ .../comments/rescript/bindings/Axios.res | 1 + .../comments/rescript/bindings/Marked.res | 2 + .../bindings/ReactTransitionGroup.res | 26 +++ client/app/packs/client-bundle.js | 2 + client/app/packs/server-bundle.js | 2 + config/routes.rb | 1 + lib/tasks/ci.rake | 11 +- lib/tasks/linters.rake | 2 +- package.json | 10 ++ spec/rescript/rescript_spec.rb | 76 +++++++++ yarn.lock | 30 ++++ 31 files changed, 740 insertions(+), 4 deletions(-) create mode 100644 app/views/pages/rescript.html.erb create mode 100644 bsconfig.json create mode 100644 client/app/bundles/comments/rescript/Actions/Actions.res create mode 100644 client/app/bundles/comments/rescript/CommentForm/CommentForm.res create mode 100644 client/app/bundles/comments/rescript/CommentForm/forms/HorizontalForm.res create mode 100644 client/app/bundles/comments/rescript/CommentForm/forms/InlineForm.res create mode 100644 client/app/bundles/comments/rescript/CommentForm/forms/StackedFrom.res create mode 100644 client/app/bundles/comments/rescript/CommentList/AlertError/AlertError.res create mode 100644 client/app/bundles/comments/rescript/CommentList/Comment/Comment.res create mode 100644 client/app/bundles/comments/rescript/CommentList/CommentList.res create mode 100644 client/app/bundles/comments/rescript/Header/Header.res create mode 100644 client/app/bundles/comments/rescript/ReScriptShow.module.scss create mode 100644 client/app/bundles/comments/rescript/ReScriptShow.res create mode 100644 client/app/bundles/comments/rescript/bindings/Axios.res create mode 100644 client/app/bundles/comments/rescript/bindings/Marked.res create mode 100644 client/app/bundles/comments/rescript/bindings/ReactTransitionGroup.res create mode 100644 spec/rescript/rescript_spec.rb diff --git a/.eslintignore b/.eslintignore index e9152576..726bf604 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,4 @@ public/ client/app/libs/i18n/translations.js client/app/libs/i18n/default.js postcss.config.js +client/app/bundles/comments/rescript/ diff --git a/.github/workflows/rspec_test.yml b/.github/workflows/rspec_test.yml index 928c1663..45551db7 100644 --- a/.github/workflows/rspec_test.yml +++ b/.github/workflows/rspec_test.yml @@ -73,6 +73,9 @@ jobs: - name: Build i18n libraries run: bundle exec rake react_on_rails:locale + - name: Build Rescript components + run: yarn res:build + - name: Build shakapacker chunks run: NODE_ENV=development bundle exec bin/shakapacker diff --git a/.gitignore b/.gitignore index 1743c0ba..42b8d30f 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,8 @@ client/app/libs/i18n/default.js /yarn-error.log yarn-debug.log* .yarn-integrity + +lib/bs +/lib/ocaml + +client/app/bundles/comments/rescript/**/*.bs.js diff --git a/Procfile.dev b/Procfile.dev index 49aba946..20453bbe 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,5 +1,6 @@ # Procfile for development using HMR # You can run these commands in separate shells +rescript: yarn res:dev redis: redis-server rails: bundle exec rails s -p 3000 wp-client: HMR=true RAILS_ENV=development NODE_ENV=development bin/shakapacker-dev-server diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 2ebf76ed..c9f6543e 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -36,6 +36,8 @@ def no_router def simple; end + def rescript; end + private def set_comments diff --git a/app/views/pages/rescript.html.erb b/app/views/pages/rescript.html.erb new file mode 100644 index 00000000..8afe2d40 --- /dev/null +++ b/app/views/pages/rescript.html.erb @@ -0,0 +1 @@ +<%= react_component "RescriptShow", prerender: true %> diff --git a/bsconfig.json b/bsconfig.json new file mode 100644 index 00000000..1f842dce --- /dev/null +++ b/bsconfig.json @@ -0,0 +1,28 @@ +{ + "name": "react-webpack-rails-tutorial", + "sources": [ + { + "dir": "client/app/bundles/comments/rescript", + "subdirs": true + } + ], + "package-specs": [ + { + "module": "es6", + "in-source": true + } + ], + "bsc-flags": ["-open JsonCombinators", "-open Belt"], + "suffix": ".bs.js", + "bs-dependencies": [ + "@rescript/react", + "@rescript/core", + "@glennsl/rescript-fetch", + "@glennsl/rescript-json-combinators", + "rescript-react-on-rails" + ], + "jsx": { + "version": 4, + "mode": "automatic" + } +} diff --git a/client/app/bundles/comments/components/NavigationBar/NavigationBar.jsx b/client/app/bundles/comments/components/NavigationBar/NavigationBar.jsx index 52e3a477..65dc3b40 100644 --- a/client/app/bundles/comments/components/NavigationBar/NavigationBar.jsx +++ b/client/app/bundles/comments/components/NavigationBar/NavigationBar.jsx @@ -97,6 +97,14 @@ function NavigationBar(props) { Classic Rails +
  • + + Rescript + +
  • { + let _ = await Axios.post( + "comments.json", + { + "author": comment.author, + "text": comment.text, + }, + { + "responseType": "json", + "headers": { + // see https://github.com/shakacode/react_on_rails/blob/249c69812474e0f532df432581bf5e618df0e1ec/node_package/src/Authenticity.ts#L13C1-L18C1 + "X-CSRF-Token": ReactOnRails.authenticityToken(), + "X-Requested-With": "XMLHttpRequest", + }, + }, + ) + } +} + +module Fetch = { + type t = { + author: string, + text: string, + id: int, + } + + type comments = array + + type commentsRes = {comments: comments} + + let fetchComments = async (): result => { + open Json.Decode + + let response = await Fetch.get("comments.json") + let jsonRes = await response->Fetch.Response.json + + let jsonComment = Json.Decode.object(field => { + author: field.required(. "author", string), + text: field.required(. "text", string), + id: field.required(. "id", int), + }) + + let jsonComments = Json.Decode.object(field => { + comments: field.required(. "comments", array(jsonComment)), + }) + + switch jsonRes->Json.decode(jsonComments) { + | Ok(decodedRes) => Ok(decodedRes.comments) + | Error(e) => Error(e) + } + } +} diff --git a/client/app/bundles/comments/rescript/CommentForm/CommentForm.res b/client/app/bundles/comments/rescript/CommentForm/CommentForm.res new file mode 100644 index 00000000..db964269 --- /dev/null +++ b/client/app/bundles/comments/rescript/CommentForm/CommentForm.res @@ -0,0 +1,149 @@ +type formDisplay = + | Horizontal + | Inline + | Stacked + +type savingStatus = + | Idle + | Saving + | Error + +type formData = { + formName: string, + formType: formDisplay, +} + +type state = { + author: string, + text: string, + form: formDisplay, + savingStatus: savingStatus, +} + +type action = + | SetAuthor(string) + | SetText(string) + | SetFormType(formDisplay) + | SetSavingError + | ClearSavingError + | SetStoreStatusSaving + +let reducer = (state: state, action: action): state => { + switch action { + | SetAuthor(author) => {...state, author} + | SetText(text) => {...state, text} + | SetFormType(form) => {...state, form} + | SetSavingError => {...state, savingStatus: Error} + | ClearSavingError => {...state, savingStatus: Idle} + | SetStoreStatusSaving => {...state, savingStatus: Saving} + } +} + +@react.component +let make = (~fetchData) => { + let (state, dispatch) = React.useReducer( + reducer, + { + author: "", + text: "", + form: Horizontal, + savingStatus: Idle, + }, + ) + + let disabled = React.useMemo1(() => { + switch state.savingStatus { + | Saving => true + | Idle + | Error => false + } + }, [state.savingStatus]) + + let storeComment = (newComment: Actions.Create.t) => { + SetStoreStatusSaving->dispatch + let saveAndFetchComments = async () => { + try { + let _ = await Actions.Create.storeComment(newComment) + ClearSavingError->dispatch + + await fetchData() + } catch { + | _ => SetSavingError->dispatch + } + } + saveAndFetchComments()->ignore + } + + let handleAuthorChange = event => { + let value = ReactEvent.Form.currentTarget(event)["value"] + SetAuthor(value)->dispatch + } + + let handleTextChange = event => { + let value = ReactEvent.Form.currentTarget(event)["value"] + SetText(value)->dispatch + } + + let handleSubmit = event => { + ReactEvent.Form.preventDefault(event) + storeComment({author: state.author, text: state.text}) + } + + let forms: array = [ + {formName: "Horizontal Form", formType: Horizontal}, + {formName: "Inline Form", formType: Inline}, + {formName: "Stacked Form", formType: Stacked}, + ] + +
    +
    + {forms + ->Array.map(form => + + ) + ->React.array} +
    +
    + {switch state.form { + | Horizontal => + + | Stacked => + + | Inline => + + }} + {switch state.savingStatus { + | Error => + | Idle + | Saving => React.null + }} +
    +} diff --git a/client/app/bundles/comments/rescript/CommentForm/forms/HorizontalForm.res b/client/app/bundles/comments/rescript/CommentForm/forms/HorizontalForm.res new file mode 100644 index 00000000..8c4bbab9 --- /dev/null +++ b/client/app/bundles/comments/rescript/CommentForm/forms/HorizontalForm.res @@ -0,0 +1,37 @@ +@react.component +let make = (~author, ~handleAuthorChange, ~text, ~handleTextChange, ~handleSubmit, ~disabled) => { +
    +
    + + +
    +
    + + +
    +
    +
    + +
    + +} diff --git a/client/app/bundles/comments/rescript/CommentForm/forms/InlineForm.res b/client/app/bundles/comments/rescript/CommentForm/forms/InlineForm.res new file mode 100644 index 00000000..b882cea7 --- /dev/null +++ b/client/app/bundles/comments/rescript/CommentForm/forms/InlineForm.res @@ -0,0 +1,40 @@ +@react.component +let make = (~author, ~handleAuthorChange, ~text, ~handleTextChange, ~handleSubmit, ~disabled) => { +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +} diff --git a/client/app/bundles/comments/rescript/CommentForm/forms/StackedFrom.res b/client/app/bundles/comments/rescript/CommentForm/forms/StackedFrom.res new file mode 100644 index 00000000..1daedcfa --- /dev/null +++ b/client/app/bundles/comments/rescript/CommentForm/forms/StackedFrom.res @@ -0,0 +1,37 @@ +@react.component +let make = (~author, ~handleAuthorChange, ~text, ~handleTextChange, ~handleSubmit, ~disabled) => { +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +} diff --git a/client/app/bundles/comments/rescript/CommentList/AlertError/AlertError.res b/client/app/bundles/comments/rescript/CommentList/AlertError/AlertError.res new file mode 100644 index 00000000..122726c2 --- /dev/null +++ b/client/app/bundles/comments/rescript/CommentList/AlertError/AlertError.res @@ -0,0 +1,26 @@ +@module("../../ReScriptShow.module.scss") external css: {..} = "default" + +@react.component +let make = (~errorMsg: string) => { + let nodeRef = React.useRef(Js.Nullable.null) + + let cssTransitionGroupClassNames: ReactTransitionGroup.CSSTransition.t = { + enter: css["elementEnter"], + enterActive: css["elementEnterActive"], + exit: css["elementLeave"], + exitActive: css["elementLeaveActive"], + } + + // The 500 must correspond to the 0.5s in: + // ../../RescriptShow.module.scss:9 + +
    + {errorMsg->React.string} +
    +
    +} diff --git a/client/app/bundles/comments/rescript/CommentList/Comment/Comment.res b/client/app/bundles/comments/rescript/CommentList/Comment/Comment.res new file mode 100644 index 00000000..817fe6b1 --- /dev/null +++ b/client/app/bundles/comments/rescript/CommentList/Comment/Comment.res @@ -0,0 +1,19 @@ +@react.component +let make = (~comment: Actions.Fetch.t, ~cssTransitionGroupClassNames) => { + let rawMarkup = Marked.marked(comment.text, {gfm: true}) + let innerHTML = {"__html": rawMarkup} + let nodeRef = React.useRef(Js.Nullable.null) + + // The 500 must correspond to the 0.5s in: + // ../../RescriptShow.module.scss:9 + +
    +

    {comment.author->React.string}

    + +
    +
    +} diff --git a/client/app/bundles/comments/rescript/CommentList/CommentList.res b/client/app/bundles/comments/rescript/CommentList/CommentList.res new file mode 100644 index 00000000..1a433257 --- /dev/null +++ b/client/app/bundles/comments/rescript/CommentList/CommentList.res @@ -0,0 +1,23 @@ +@module("../ReScriptShow.module.scss") external css: {..} = "default" + +@react.component +let make = (~comments: Actions.Fetch.comments) => { + let cssTransitionGroupClassNames: ReactTransitionGroup.CSSTransition.t = { + enter: css["elementEnter"], + enterActive: css["elementEnterActive"], + exit: css["elementLeave"], + exitActive: css["elementLeaveActive"], + } + +
    + + {comments + ->Array.map(comment => + Int.toString} + /> + ) + ->React.array} + +
    +} diff --git a/client/app/bundles/comments/rescript/Header/Header.res b/client/app/bundles/comments/rescript/Header/Header.res new file mode 100644 index 00000000..8c794489 --- /dev/null +++ b/client/app/bundles/comments/rescript/Header/Header.res @@ -0,0 +1,55 @@ +@react.component +let make = () => { +
    + +
    +
    +} diff --git a/client/app/bundles/comments/rescript/ReScriptShow.module.scss b/client/app/bundles/comments/rescript/ReScriptShow.module.scss new file mode 100644 index 00000000..01a74159 --- /dev/null +++ b/client/app/bundles/comments/rescript/ReScriptShow.module.scss @@ -0,0 +1,20 @@ +// The 0.5s must correspond to the 500s in: +// ./CommentList/AlertError/AlertError.res:10 +// ./CommentList/Comment/Comment.res:18 +.elementEnter { + opacity: 0.01; + + &.elementEnterActive { + opacity: 1; + transition: opacity 0.5s ease-in; + } +} + +.elementLeave { + opacity: 1; + + &.elementLeaveActive { + opacity: 0.01; + transition: opacity 0.5s ease-in; + } +} diff --git a/client/app/bundles/comments/rescript/ReScriptShow.res b/client/app/bundles/comments/rescript/ReScriptShow.res new file mode 100644 index 00000000..4c64f8b2 --- /dev/null +++ b/client/app/bundles/comments/rescript/ReScriptShow.res @@ -0,0 +1,66 @@ +type commentsFetchStatus = + | FetchError + | CommentsFetched(Actions.Fetch.comments) + +type state = {commentsFetchStatus: commentsFetchStatus} + +type action = + | SetComments(Actions.Fetch.comments) + | SetFetchError + +let reducer = (_, action: action): state => { + switch action { + | SetComments(comments) => {commentsFetchStatus: CommentsFetched(comments)} + | SetFetchError => {commentsFetchStatus: FetchError} + } +} + +@react.component +let default = () => { + let (state, dispatch) = React.useReducer( + reducer, + { + commentsFetchStatus: CommentsFetched(([]: Actions.Fetch.comments)), + }, + ) + + let fetchData = async () => { + let comments = await Actions.Fetch.fetchComments() + switch comments { + | Ok(comments) => SetComments(comments)->dispatch + | Error(_) => SetFetchError->dispatch + } + } + + React.useEffect1(_ => { + fetchData()->ignore + None + }, []) + +
    +

    + {"Rescript + Rails Backend (with "->React.string} + + {"react_on_rails gem"->React.string} + + {")"->React.string} +

    +
    +
    +

    {"Comments"->React.string}

    +
      +
    • {"Text supports Github Flavored Markdown."->React.string}
    • +
    • {"Comments older than 24 hours are deleted."->React.string}
    • +
    • {"Name is preserved. Text is reset, between submits"->React.string}
    • +
    • + {"To see Action Cable instantly update two browsers, open two browsers and submit a comment!"->React.string} +
    • +
    + + {switch state.commentsFetchStatus { + | FetchError => + | CommentsFetched(comments) => + }} +
    +
    +} diff --git a/client/app/bundles/comments/rescript/bindings/Axios.res b/client/app/bundles/comments/rescript/bindings/Axios.res new file mode 100644 index 00000000..932abc48 --- /dev/null +++ b/client/app/bundles/comments/rescript/bindings/Axios.res @@ -0,0 +1 @@ +@module("axios") external post: (string, {..}, {..}) => promise = "post" diff --git a/client/app/bundles/comments/rescript/bindings/Marked.res b/client/app/bundles/comments/rescript/bindings/Marked.res new file mode 100644 index 00000000..782b00be --- /dev/null +++ b/client/app/bundles/comments/rescript/bindings/Marked.res @@ -0,0 +1,2 @@ +type markedOptions = {gfm: bool} +@module("marked") external marked: (string, markedOptions) => string = "marked" diff --git a/client/app/bundles/comments/rescript/bindings/ReactTransitionGroup.res b/client/app/bundles/comments/rescript/bindings/ReactTransitionGroup.res new file mode 100644 index 00000000..5acdd747 --- /dev/null +++ b/client/app/bundles/comments/rescript/bindings/ReactTransitionGroup.res @@ -0,0 +1,26 @@ +module TransitionGroup = { + @react.component @module("react-transition-group") + external make: ( + ~children: React.element, + ~className: string, + ~component: string, + ) => React.element = "TransitionGroup" +} + +module CSSTransition = { + type t = { + enter: string, + enterActive: string, + exit: string, + exitActive: string, + } + + @react.component @module("react-transition-group") + external make: ( + ~children: React.element, + ~key: string, + ~timeout: int, + ~nodeRef: React.ref>, + ~classNames: t, + ) => React.element = "CSSTransition" +} diff --git a/client/app/packs/client-bundle.js b/client/app/packs/client-bundle.js index 5365a0cc..2675009a 100644 --- a/client/app/packs/client-bundle.js +++ b/client/app/packs/client-bundle.js @@ -10,6 +10,7 @@ import routerCommentsStore from '../bundles/comments/store/routerCommentsStore'; import commentsStore from '../bundles/comments/store/commentsStore'; import NavigationBarApp from '../bundles/comments/startup/NavigationBarApp'; import Footer from '../bundles/comments/components/Footer/Footer'; +import RescriptShow from '../bundles/comments/rescript/ReScriptShow.bs.js'; import '../assets/styles/application'; @@ -25,6 +26,7 @@ ReactOnRails.register({ NavigationBarApp, SimpleCommentScreen, Footer, + RescriptShow, }); ReactOnRails.registerStore({ diff --git a/client/app/packs/server-bundle.js b/client/app/packs/server-bundle.js index 4cec29f5..96b56491 100644 --- a/client/app/packs/server-bundle.js +++ b/client/app/packs/server-bundle.js @@ -8,6 +8,7 @@ import NavigationBarApp from '../bundles/comments/startup/NavigationBarApp'; import routerCommentsStore from '../bundles/comments/store/routerCommentsStore'; import commentsStore from '../bundles/comments/store/commentsStore'; import Footer from '../bundles/comments/components/Footer/Footer'; +import RescriptShow from '../bundles/comments/rescript/ReScriptShow.bs.js'; ReactOnRails.register({ App, @@ -15,6 +16,7 @@ ReactOnRails.register({ NavigationBarApp, SimpleCommentScreen, Footer, + RescriptShow, }); ReactOnRails.registerStore({ diff --git a/config/routes.rb b/config/routes.rb index ab6d4132..1d8c7b7a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,6 +9,7 @@ root "pages#index" get "simple", to: "pages#simple" + get "rescript", to: "pages#rescript" get "no-router", to: "pages#no_router" # React Router needs a wildcard diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake index dcea8e00..29a4dc04 100644 --- a/lib/tasks/ci.rake +++ b/lib/tasks/ci.rake @@ -13,10 +13,15 @@ if Rails.env.development? || Rails.env.test? sh "rspec" end + task build_rescript: :environment do + puts Rainbow("Building ReScript files").green + sh "yarn res:build" + end + namespace :ci do desc "Run all audits and tests" # rspec_tests must be before lint and js_tests to build the locale files - task all: %i[environment rspec_tests lint js_tests] do + task all: %i[environment build_rescript rspec_tests lint js_tests] do puts "All CI tasks" puts Rainbow("PASSED").green puts "" @@ -28,7 +33,7 @@ if Rails.env.development? || Rails.env.test? end desc "Run CI rspec tests" - task rspec: %i[environment rspec_tests] do + task rspec: %i[environment build_rescript rspec_tests] do puts "CI rspec tests" puts Rainbow("PASSED").green puts "" @@ -40,7 +45,7 @@ if Rails.env.development? || Rails.env.test? end desc "Run CI js_tests" - task js: %i[environment js_tests] do + task js: %i[environment build_rescript js_tests] do puts "CI js_tests" puts Rainbow("PASSED").green puts "" diff --git a/lib/tasks/linters.rake b/lib/tasks/linters.rake index de6637dc..e8035ce6 100644 --- a/lib/tasks/linters.rake +++ b/lib/tasks/linters.rake @@ -50,7 +50,7 @@ if %w[development test].include? Rails.env desc "See docs for task 'scss_lint'" task scss: :scss_lint - task lint: %i[rubocop js scss] do + task lint: %i[build_rescript rubocop js scss] do puts "Completed all linting" end end diff --git a/package.json b/package.json index b1f5a166..f8fbf322 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,10 @@ }, "homepage": "https://github.com/shakacode/react-webpack-rails-tutorial", "scripts": { + "res:clean": "rescript clean", + "res:format": "rescript format -all", + "res:dev": "yarn res:clean && rescript build -w", + "res:build": "yarn res:clean && rescript build", "lint:eslint": "yarn eslint client --ext \".js,.jsx,.ts\"", "lint:prettier": "yarn prettier \"**/*.@(js|jsx)\" --list-different", "lint": " yarn lint:eslint --fix && yarn lint:prettier --w", @@ -33,10 +37,14 @@ "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", "@babel/runtime": "^7.17.9", + "@glennsl/rescript-fetch": "^0.2.0", + "@glennsl/rescript-json-combinators": "^1.2.1", "@hotwired/stimulus": "^3.2.1", "@hotwired/stimulus-webpack-helpers": "^1.0.1", "@hotwired/turbo-rails": "^7.3.0", "@rails/actioncable": "7.0.5", + "@rescript/core": "^0.5.0", + "@rescript/react": "^0.11.0", "autoprefixer": "^10.4.14", "axios": "^0.21.1", "babel-loader": "^9.1.2", @@ -79,6 +87,8 @@ "react-transition-group": "4.4.5", "redux": "^4.2.1", "redux-thunk": "^2.2.0", + "rescript": "^10.1.4", + "rescript-react-on-rails": "^1.0.1", "resolve-url-loader": "^2.2.0", "sanitize-html": "^2.11.0", "sass": "^1.58.3", diff --git a/spec/rescript/rescript_spec.rb b/spec/rescript/rescript_spec.rb new file mode 100644 index 00000000..aedc5aa9 --- /dev/null +++ b/spec/rescript/rescript_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe "with Rescript" do + describe "tabs change on click" do + before do + visit "/rescript" + end + + it "shows horizontal tab on visit" do + page.has_css?("form-horizontal") + end + + it "stops showing horizontal tab when other tab is clicked" do + find("button", text: "Inline Form") + page.has_no_css?("form-horizontal") + end + + it "shows inline form when Inline Form link is clicked" do + find("button", text: "Inline Form", wait: 10) + page.has_css?("form-inline") + end + + it "shows stacked form when Stacked Form link is clicked" do + find("button", text: "Stacked Form") + page.has_no_css?("form-inline") and page.has_no_css?("form-horizontal") + end + end + + describe "form submission functions" do + let(:comment) { Comment.new(author: "Author", text: "This is a comment") } + let(:author_field) { "comment_author" } + let(:text_field) { "comment_text" } + + before do + visit "/rescript" + end + + it "adds a new comment to the page" do + fill_in author_field, with: comment.author + fill_in text_field, with: comment.text + click_button("Post") + expect(page).to have_selector "h2", text: comment.author + end + + it "comment count increases with successful form submission" do + initital_comment_count = Comment.all.count + new_comment_count = initital_comment_count + 1 + fill_in author_field, with: comment.author + fill_in text_field, with: comment.text + click_button("Post") + expect(Comment.all.count).to equal(new_comment_count) + end + + it "comment count remains the same when author field is empty" do + initial_comment_count = Comment.all.count + fill_in text_field, with: comment.text + click_button("Post") + expect(Comment.all.count).to equal(initial_comment_count) + end + + it "comment count remains the same when text field is empty" do + initial_comment_count = Comment.all.count + fill_in author_field, with: comment.author + click_button("Post") + expect(Comment.all.count).to equal(initial_comment_count) + end + + it "comment count remains the same when both form fields are empty" do + initial_comment_count = Comment.all.count + click_button("Post") + expect(Comment.all.count).to equal(initial_comment_count) + end + end +end diff --git a/yarn.lock b/yarn.lock index f669c60c..efbbf832 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1407,6 +1407,16 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@glennsl/rescript-fetch@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@glennsl/rescript-fetch/-/rescript-fetch-0.2.0.tgz#b72085d8bb19d8b9266aaf18d59c440ddceaf8dd" + integrity sha512-0tsEqJ/6/WBm02prM4RYG+qpnNTaB8QKKIeQHXdDaE4C5YfA/nzjxMNW3CjsGIaEgyrAmmIXFS0kx24UjvOI6A== + +"@glennsl/rescript-json-combinators@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@glennsl/rescript-json-combinators/-/rescript-json-combinators-1.2.1.tgz#3f641e4548ac1a664ce9acb140d5bba9ee1d5c9e" + integrity sha512-2Tw7NtrPerxN+9Y0u9bl9rUgqUqFcZkkRYELjHu9mIT/PDG3n8lO/osrwMIfoo6ze2FAur0Fw8hfDRJV8n3W3A== + "@hotwired/stimulus-webpack-helpers@^1.0.0", "@hotwired/stimulus-webpack-helpers@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@hotwired/stimulus-webpack-helpers/-/stimulus-webpack-helpers-1.0.1.tgz#4cd74487adeca576c9865ac2b9fe5cb20cef16dd" @@ -1853,6 +1863,16 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.2.tgz#cba1cf0a04bc04cb66027c51fa600e9cbc388bc8" integrity sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A== +"@rescript/core@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@rescript/core/-/core-0.5.0.tgz#3e83e75aaa60f42f4279e0d184358f2db5d40955" + integrity sha512-Keqnpi+8VqyhCk/3aMwar8hJbNy2IsINAAfIFeQC65IIegCR0QXFDBpQxfVcmbbtoHq6HnW4B3RLm/9GCUJQhQ== + +"@rescript/react@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@rescript/react/-/react-0.11.0.tgz#d2545546d823bdb8e6b59daa1790098d1666f79e" + integrity sha512-RzoAO+3cJwXE2D7yodMo4tBO2EkeDYCN/I/Sj/yRweI3S1CY1ZBOF/GMcVtjeIurJJt7KMveqQXTaRrqoGZBBg== + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -8218,6 +8238,16 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== +rescript-react-on-rails@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rescript-react-on-rails/-/rescript-react-on-rails-1.0.1.tgz#541dffdae64ec5053a50a3792b9db8783c959d1b" + integrity sha512-sbkDNCoiEWM9rqIiu+4joAj6W92yhM64KtLZQYfvYYm578jMcG02d98xpDeBT7MxZoPZZggFIed0m6Dj8bbDYA== + +rescript@^10.1.4: + version "10.1.4" + resolved "https://registry.yarnpkg.com/rescript/-/rescript-10.1.4.tgz#0f37710d371f32a704f17b4e804f66ce3c79a305" + integrity sha512-FFKlS9AG/XrLepWsyw7B+A9DtQBPWEPDPDKghV831Y2KGbie+eeFBOS0xtRHp0xbt7S0N2Dm6hhX+kTZQ/3Ybg== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"