[
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"root\": true,\n  \"parser\": \"@typescript-eslint/parser\",\n  \"env\": {\n      \"browser\": true,\n      \"es2020\": true,\n      \"jest\": true\n  },\n  \"globals\": {\n    \"chrome\": \"readonly\"\n  },\n  \"extends\": [\n      \"airbnb\",\n      // added below for npm install -D eslint-config-airbnb-typescript --legacy-peer-deps\n      \"airbnb-typescript\",\n      \"prettier\",\n      \"prettier/react\"\n  ],\n  \"plugins\": [\"prettier\"],\n  \"parserOptions\": {\n      \"ecmaVersion\": 2022,\n      \"sourceType\": \"module\",\n      \"ecmaFeatures\": {\n        \"jsx\": true\n      },\n      \"project\": \"./package/tsconfig.json\",\n      \"tsconfigRootDir\": \"__dirname\"\n  },\n  \"settings\": {\n    \"import/extensions\": [\n        \".js\",\n        \".jsx\",\n        \".ts\",\n        \".tsx\"\n    ],\n    \"import/parsers\": {\n        \"@typescript-eslint/parser\": [\n            \".ts\",\n            \".tsx\"\n        ]\n    },\n    \"import/resolver\": {\n        \"typescript\": {\n            \"directory\": \"./package/tsconfig.json\"\n        },\n        \"node\": {\n            \"extensions\": [\n                \".js\",\n                \".jsx\",\n                \".ts\",\n                \".tsx\"\n            ]\n        }\n    },\n  \"rules\": {\n      \"prettier/prettier\": [\"warn\"],\n      \"arrow-body-style\": [\"error\", \"as-needed\"],\n      \"default-case-last\": \"error\",\n      \"default-param-last\": [\"error\"],\n      \"func-style\": [\"off\", \"expression\"],\n      \"no-constant-condition\": \"error\",\n      \"no-useless-call\": \"error\",\n      \"prefer-exponentiation-operator\": \"error\",\n      \"prefer-regex-literals\": \"error\",\n      \"quotes\": [\n          \"error\",\n          \"single\",\n          {\n              \"avoidEscape\": true,\n              \"allowTemplateLiterals\": false\n          }\n      ],\n      \"import/prefer-default-export\": \"off\",\n      \"import/extensions\": [\n        \"error\",\n        \"ignorePackages\",\n        {\n            \"js\": \"never\",\n            \"jsx\": \"never\",\n            \"ts\": \"never\",\n            \"tsx\": \"never\"\n        }\n      ],\n      \"react/jsx-filename-extension\": [\"off\"],\n      \"react/function-component-definition\": [\n          \"error\",\n          {\n              \"namedComponents\": \"arrow-function\",\n              \"unnamedComponents\": \"arrow-function\"\n          }\n      ],\n      \"react/jsx-handler-names\": [\n          \"error\",\n          {\n              \"eventHandlerPrefix\": \"handle\",\n              \"eventHandlerPropPrefix\": \"on\"\n          }\n      ],\n      \"react/jsx-key\": \"error\",\n      \"react/jsx-no-useless-fragment\": \"error\",\n      \"react/jsx-sort-props\": [\n          \"error\",\n          {\n              \"callbacksLast\": true,\n              \"shorthandFirst\": true,\n              \"shorthandLast\": false,\n              \"ignoreCase\": true,\n              \"noSortAlphabetically\": false,\n              \"reservedFirst\": true\n          }\n      ],\n      \"react/no-adjacent-inline-elements\": \"error\",\n      \"react/no-direct-mutation-state\": \"error\",\n      \"react/no-multi-comp\": \"error\",\n      \"react/prop-types\": [\n          \"error\",\n          { \"skipUndeclared\": true }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n\n**Smartphone (please complete the following information):**\n - Device: [e.g. iPhone6]\n - OS: [e.g. iOS8.1]\n - Browser [e.g. stock browser, safari]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"npm\"\n    directory: \"/package\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Types of changes\n<!--- What types of changes does your code introduce to Scratch Project? Put an `x` in the boxes that apply. -->\n- [ ] Bugfix (change which fixes an issue)\n- [ ] New feature (change which adds functionality)\n- [ ] Refactor (change which changes the codebase without affecting its external behavior)\n- [ ] Non-breaking change (fix or feature that would causes existing functionality to work as expected)\n- [ ] Breaking change (fix or feature that would cause existing functionality to __not__ work as expected)\n## Purpose\n<!--- Describe the problem or feature. Link to the issue(s) fixed by this pull request if applicable. -->\n## Approach\n<!--- How does your change address the problem? -->\n## Resources\n<!--- Describe the research stage. Link to any blog posts, video, patterns, libraries, addons, or other resources that helped you to solve this problem. -->\n## Screenshot(s)\n<!--- (if applicable--you can delete otherwise) -->\n<!--- Include a screenshot here if the change you made changes the look of the site in any way! -->\n"
  },
  {
    "path": ".gitignore",
    "content": "# npm\nnode_modules\n\n# System files\n.DS_Store\n.vscode\n\n# Tests\ncoverage\n\n# Package\npackage/build\n\n# Demo App\nchromogen.test.js\npackage-lock.json\nTODO.md"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n\t\"printWidth\": 100,\n  \"tabWidth\": 2,\n  \"useTabs\": false,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"quoteProps\": \"as-needed\",\n  \"jsxSingleQuote\": false,\n  \"trailingComma\": \"all\",\n  \"bracketSpacing\": true,\n  \"jsxBracketSameLine\": false,\n\t\"arrowParens\": \"always\",\n  \"endOfLine\": \"lf\"\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - 14\nenv:\n  - TEST_DIR=package\nbefore_install:\n  - cd $TEST_DIR\ninstall:\n  - npm install\nscript:\n  - npm run test\n  - npm run coveralls\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at chromogen.app@gmail.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM jenkins/jenkins:2.375.3\nUSER root\nRUN apt-get update && apt-get install -y lsb-release\nRUN curl -fsSLo /usr/share/keyrings/docker-archive-keyring.asc \\\n  https://download.docker.com/linux/debian/gpg\nRUN echo \"deb [arch=$(dpkg --print-architecture) \\\n  signed-by=/usr/share/keyrings/docker-archive-keyring.asc] \\\n  https://download.docker.com/linux/debian \\\n  $(lsb_release -cs) stable\" > /etc/apt/sources.list.d/docker.list\nRUN apt-get update && apt-get install -y docker-ce-cli\nUSER jenkins\nRUN jenkins-plugin-cli --plugins \"blueocean docker-workflow\""
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 OSLabs Beta\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n<a href=\"https://chromogen-site-eight.vercel.app/\">\n  <img\n    height=\"200\"\n    width=\"450\"\n    alt=\"Chromogen logo\"\n    src=\"./assets/logo/Chromogen.png\"\n  />\n</a>\n\n<h3>A UI-driven test-generation package for <a href= https://github.com/pmndrs/zustand> Zustand</a> Stores and <a href=\"https://github.com/facebookexperimental/Recoil\">Recoil.js</a> selectors.</h3>\n\n<br />\n\n[![npm version](https://img.shields.io/npm/v/chromogen)](https://www.npmjs.com/package/chromogen)\n[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/open-source-labs/Chromogen/blob/master/LICENSE)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=CHROMOGEN%20-%20A%20UI-driven%20Jest%20test%20generator%20for%20Recoil%20apps%0A&url=https://www.npmjs.com/package/Chromogen&hashtags=React,Recoil,Jest,testing)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)\n[![npm downloads](https://img.shields.io/npm/dm/chromogen)](https://www.npmjs.com/package/chromogen)\n[![Github stars](https://img.shields.io/github/stars/open-source-labs/Chromogen?style=social)](https://github.com/open-source-labs/Chromogen)\n<br />\n\n</div>\n<br />\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Supported Tests](#supported-tests)\n- [Installing the Package](#installing-the-package)\n- [Installation for Zustand Apps](#installation-for-zustand-apps)\n- [Installation for Recoil Apps](#installation-for-recoil-apps)\n- [Usage for All Apps](#usage-for-all-apps)\n- [Contributor Setup](#contributor-setup)\n- [Test Setup](#test-setup)\n- [CI/CD with Jenkins](#jenkins)\n- [Demo Apps](#demo-apps)\n- [Contributing](#contributing)\n- [Core Team](#core-team)\n- [License](#license)\n  <Br><br />\n\n## Overview\n\nYou're an independent developer or part of a lean team. You want reliable unit tests for your new Zustand or React-Recoil app, but you need to move fast and time is a major constraint. More importantly, you want your tests to reflect how your users interact with the application, rather than testing implementation details.\n<br><Br>\n\n[Enter Chromogen - Now on version 4.0](https://www.npmjs.com/package/chromogen). Chromogen is a Jest unit-test generation tool for Zustand Stores and Recoil selectors. It captures state changes during user interaction and auto-generates corresponding test suites. Simply launch your application after following the installation instructions below, interact as a user normally would, and with one click you can download a ready-to-run Jest test file. Alternatively, you can copy the generated tests straight to your clipboard.\n<b> Chromogen is now compatible with React V18! </b>\n\n<br><hr>\n\n## Supported Tests\n\n<b>Zustand Tests</b>\n\nChromogen currently supports two types of testing for Zustand applications:\n\n1. **Initial Store State** on page load.\n2. **Store State Changes** whenever an action is invoked on the store.\n\nOn initial render, Chromogen captures store state as a whole and keeps track of any subsequent state changes. In order to generate tests, you'll need to make some changes to how your store is created.\n\nTo use Chromogen with your Zustand application, please see the [Installation for Zustand Apps](#installation-for-zustand-apps) section below.\n\n<br>\n<b>Recoil Tests</b>\n\nChromogen currently supports three main types of tests for Recoil apps:\n\n1. **Initial selector values** on page load\n2. **Selector return values** for a given state, using snapshots captured after each state transaction.\n3. **Selector _set_ logic** asserting on resulting atom values for a given `newValue` argument and starting state.\n\nThese test suites will be captured for _synchronous_ selectors and selectorFamilies only. However, the presence of asyncronous selectors in your app should not cause any issues with the generated tests. Chromogen can identify such selectors at run-time and exclude them from capture.\n\nAt this time, we have no plans to introduce testing for async selectors; the mocking requirements are too opaque and fragile to accurately capture at runtime.\n\nBy default, Chromogen uses atom and selector keys to populate the import & hook statements in the test file. If your source code does _not_ use matching variable and key names, you will need to pass the imported atoms and selectors to the Chromogen\ncomponent as a `store` prop. The installation instructions below contain further details.\n\n<br><hr>\n\n## Installing the Package\n\n```\nnpm install chromogen\n```\n\n<br><hr>\n\n## Installation for Zustand Apps\n\nBefore using Chromogen, you'll need to make two changes to your application:\n\n1. Import the `<ChromogenZustandObserver />` component and render it alongside any other components in `<App />`\n2. Import `chromogenZustandMiddleware` function from Chromogen. This will be used as middleware when setting up your store.\n\n### Import the ChromogenZustandObserver component\n\nImport `ChromogenZustandObserver`. ChromogenZustandObserver can be rendered alongside any other components in `<App />`.\n\n```jsx\nimport React from 'react';\nimport { ChromogenZustandObserver } from 'chromogen';\nimport TodoList from './TodoList';\n\nconst App = () => (\n  <>\n    <ChromogenZustandObserver>\n    <TodoList />\n    </ChromogenZustandObserver>\n  </>\n);\n\nexport default App;\n```\n\nImport `chromogenZustandMiddleware`. When you call create, wrap your store function with chromogenZustandMiddleware. **Note**, when using chromogenZustandMiddleware, you'll need to provide some additional arguments into the set function.\n\n1. _Overwrite State_ (boolean) - Without middleware, this defaults to `false`, but you'll need to explicitly provide a value when using Chromogen.\n2. _Action Name_ - Used for test generation\n3. _Action Parameters_ - If the action requires input parameters, pass these in after the Action Name.\n\n```jsx\nimport { chromogenZustandMiddleware } from 'chromogen';\nimport create from 'zustand';\n\nconst useStore = create(\n  chromogenZustandMiddleware((set) => ({\n    counter: 0,\n    color: 'black',\n    prioritizeTask: ['walking', 5],\n    addCounter: () => set(() => ({ counter: (counter += 1) }), false, 'addCounter'),\n    changeColor: (newColor) => set(() => ({ color: newColor }), false, 'changeColor', newColor),\n    setTaskPriority: (task, priority) =>\n      set(() => ({ prioritizeTask: [task, priority] }), false, 'setTaskPriority', task, priority),\n  })),\n);\n\nexport default useStore;\n```\n\n<br><hr>\n\n## Installation for Recoil Apps\n\nBefore running Chromogen, you'll need to make two changes to your application:\n\n1. Import the `<ChromogenObserver />` component as a child of `<RecoilRoot />`\n1. Import the `atom` and `selector` functions from Chromogen instead of Recoil\n\n<i>Note: These changes do have a small performance cost, so they should be reverted before deploying to production.</i>\n\n<br>\n\n### Import the ChromogenObserver component\n\nChromogenObserver should be included as a direct child of RecoilRoot. It does not need to wrap any other components, and it takes no mandatory props. It utilizes Recoil's TransactionObserver Hook to record snapshots on state change.\n\n```jsx\nimport React from 'react';\nimport { RecoilRoot } from 'recoil';\nimport { ChromogenObserver } from 'chromogen';\nimport MyComponent from './components/MyComponent.jsx';\n\nconst App = (props) => (\n  <RecoilRoot>\n    <ChromogenObserver />\n    <MyComponent {...props} />\n  </RecoilRoot>\n);\n\nexport default App;\n```\n\nIf you are using pseudo-random key names, such as with _UUID_, you'll need to pass all of your store exports to the ChromogenObserver component as a `store` prop. This will allow Chromogen to use source code variable names in the output file, instead of relying on keys. When all atoms and selectors are exported from a single file, you can pass the imported module directly:\n\n```jsx\nimport * as store from './store';\n// ...\n<ChromogenObserver store={store} />;\n```\n\nIf your store utilizes seprate files for various pieces of state, you can pass all of the imports in an array:\n\n```jsx\nimport * as atoms from './store/atoms';\nimport * as selectors from './store/selectors';\nimport * as misc from './store/arbitraryRecoilState';\n// ...\n<ChromogenObserver store={[atoms, selectors, misc]} />;\n```\n\n<br>\n\n### Import atom & selector functions from Chromogen\n\nWherever you import `atom` and/or `selector` functions from Recoil (typically in your `store` file), import them from Chromogen instead. The arguments passed in do **not** need to change in any away, and the return value will still be a normal RecoilAtom or RecoilSelector. Chromogen wraps the native Recoil functions to track which pieces of state have been created, as well as when various selectors are called and what values they return.\n\n```js\nimport { atom, selector } from 'chromogen';\n\nexport const fooState = atom({\n  key: 'fooState',\n  default: {},\n});\n\nexport const barState = selector({\n  key: 'barState',\n  get: ({ get }) => {\n    const derivedState = get(fooState);\n    return derivedState.baz || 'value does not exist';\n  },\n});\n```\n\n<br><hr>\n\n## Usage for All Apps\n\nAfter following the installation steps above, launch your application as normal. You should see two buttons in the bottom left corner.\n\n<div align=\"center\">\n\n![Buttons](./assets/README-root/ultratrimmedDemo.gif)\n\n</div>\n\nThe pause button on the left is the **pause recording** button. Clicking it will pause recording, so that no tests are generated during subsequent state changes. Pausing is useful for setting up a complex initial state with repetitive actions, where you don't want to test every step of the process.\n\nThe button in the middle is the **download** button. Clicking it will download a new test file that includes _all_ tests generated since the app was last launched or refreshed.\n\nThe button on the right is the **copy-to-clipboard** button. Clicking it will copy your tests, including _all_ tests generated since the app was last launched or refreshed.\n\nOnce you've recorded all the interactions you want to test, click the pause button and then the download button to generate the test file or press copy to copy to your clipboard. You can now drag-and-drop the downloaded file into your app's test directory or paste the code in your new file. **Don't forget to add the source path in your test file**\n\nYou're now ready to run your tests! After running your normal Jest test command, you should see a test suite for `chromogen.test.js`.\n\nThe current tests check whether state has changed after an interaction and checks whether the resulting state change variables have been updated as expected.\n\n<br><hr>\n## Contributor Setup\n\t\nIn order to make/observe changes to the code, you'll have to run Chromogen locally as opposed to running via NPM.\nDue to inconsistencies across different machines, it is recomended to use the following method to run Chromogen locally.\n\n<br><Br>\n**Run for demos within this directory**\n\nAfter cloning the repo, \n\t\n```jsx\n\tnpm install\n```\n\t\nfrom BOTH the /package directory (where the app lives) AND the /demo directory you're developing with.\n\t\nThen, and ONLY then, run \n\t\n```jsx\nnpm run tarballUpdate\t\n```\n<br><Br>\n\t\n**Run for local applications outside this directory**\n\nAfter cloning this repo, add the following script to your app's package.json:\n\t\n```jsx\n\"tarballUpdate\": \"npm --prefix <reference to the /package directory on your local machine> run build && npm pack <reference to the /package directory on your local machine> && npm uninstall chromogen && npm install ./chromogen-5.0.1.tgz && npm start\"\n```\n\t\n<br><hr>\t\n## Test Setup\n\n### Zustand Test Setup\n\nBefore running the test file, you'll need to specify the import path for your store by replacing `<ADD STORE FILEPATH>`. The default output assumes that all stores are imported from a single path; if that's not possible, you'll need to separately import each set of stores from their appropriate path.\n\n|                              **BEFORE**                               |                               **AFTER**                               |\n| :-------------------------------------------------------------------: | :-------------------------------------------------------------------: |\n| ![Default Filepath](./assets/README-root/zustand-test-filepath-1.png) | ![Updated Filepath](./assets/README-root/zustand-test-filepath-2.png) |\n\n<div align=\"center\">\n\n![Test Output](./assets/README-root/zustand-test-snapshot-2.png)\n\n</div>\n\n<br>\n\n---\n\n### Recoil Test Setup\n\nBefore running the test file, you'll need to specify the import path for your store by replacing `<ADD STORE FILEPATH>`. The default output assumes that all atoms and selectors are imported from a single path; if that's not possible, you'll need to separately import each set of atoms and/or selectors from their appropriate path.\n\n|                          **BEFORE**                           |                          **AFTER**                           |\n| :-----------------------------------------------------------: | :----------------------------------------------------------: |\n| ![Default Filepath](./assets/README-root/filepath-before.png) | ![Updated Filepath](./assets/README-root/filepath-after.png) |\n\nYou're now ready to run your tests! Upon running your normal Jest test command, you should see three suites for `chromogen.test.js`:\n\n<div align=\"center\">\n\n![Test Output](./assets/README-root/test-output.png)\n\n</div>\n\n**Initial Render** tests whether each selector returns the correct value at launch. There is one test per selector.\n\n**Selectors** tests the return value of various selectors for a given state. Each test represents the app state after a transaction has occured, generally triggered by some user interaction. For each selector that ran after that transaction, the test asserts on the selector's return value for the given state.\n\n**Setters** tests the state that results from setting a writeable selector with a given value and starting state. There is one test per set call, asserting on each atom's value in the resulting state.\n\n<br><hr>\n\t\n## CI/CD with Jenkins\n\t\nYou will need to have Docker installed to run Jenkins. Ru the following command to create a bridge network for Jenkins:\n\t\n```jsx\n\t\n\tdocker network create jenkins\n```\n\n\t\nEnable Docker commands to be executable with Jenkins nodes:\n\t\n```jsx\n\t\ndocker run \\\n  --name jenkins-docker \\\n  --rm \\\n  --detach \\\n  --privileged \\\n  --network jenkins \\\n  --network-alias docker \\\n  --env DOCKER_TLS_CERTDIR=/certs \\\n  --volume jenkins-docker-certs:/certs/client \\\n  --volume jenkins-data:/var/jenkins_home \\\n  --publish 2376:2376 \\\n  --publish 3003:3003 --publish 5003:5003 \\\n  docker:dind \\\n  --storage-driver overlay2\n\t\n\t\n```\n\t\nBuild a Docker image from the DOckerfile within Chromogen:\n\t\n```jsx\n\t\n\tdocker build -t chromogen-jenkins .\n\t\n```\n\t\nRun your Chromogen-Jenkins image in Docker as a container:\n\t\n```jsx\n\tdocker run \\\n  --name jenkins-blueocean \\\n  --detach \\\n  --network jenkins \\\n  --env DOCKER_HOST=tcp://docker:2376 \\\n  --env DOCKER_CERT_PATH=/certs/client \\\n  --env DOCKER_TLS_VERIFY=1 \\\n  --publish 8080:8080 \\\n  --publish 50000:50000 \\\n  --volume jenkins-data:/var/jenkins_home \\\n  --volume jenkins-docker-certs:/certs/client:ro \\\n  --volume \"$HOME\":/home \\\n  --restart=on-failure \\\n  --env JAVA_OPTS=\"-Dhudson.plugins.git.GitSCM.ALLOW_LOCAL_CHECKOUT=true\" \\\n  myjenkins-blueocean:2.375.3-1\t\n```\n\t\nNavigate to your localhost:8080 and enter the password (between the two sets of asterisks) that is generated from the following command:\n\t\n```jsx\n\tdocker logs jenkins-blueocean\t\n\t\n```\n\t\nCreate your first administrator user.\n\t\nStop and start your Docker container using one of the following:\n\t\n```jsx\n\t\n\tdocker stop jenkins-blueocean jenkins-docker\n\tdocker start jenkins-blueocean jenkins-docker\n\t\n```\n\nWhen configuring your pipeline, make sure to set your pipeline definition to 'Pipeline Script from SCM' and enter the path to your local repositiory, specify the brand you are working from, set the Script Path to 'jenkins/Jenkinsfile', and uncheck 'Lightweight Checkout'.\n\t\n\t\n\n\n\t\n## Demo Apps\n\n### Zustand Demo To-Do App\n\nChromogen's open-source Zustand demo app provides a Zustand-based frontend with multiple store properties and actions to test. You can access this demo application <a href='https://demo-zustand-todo.vercel.app/'>here</a>, and view the source code in the `demo-zustand-todo` folder of this repository.\n\n<br>\n\n### Recoil Demo To-Do App\n\nChromogen's official Recoil demo app provides a ready-to-run Recoil frontend with a number of different selector implementations to test against. It's available in the `demo-todo` folder of this repository and comes with Chromogen pre-installed; just run `npm install && npm start` to launch.\n\n<br><hr>\n\n## Contributing\n\n**We expect all contributors to abide by the standards of behavior outlined in the [Code of Conduct](CODE_OF_CONDUCT.md).**\n\nWe welcome community contributions, including new developers who've never [made an open source Pull Request before](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). If you'd like to start a new PR, we recommend [creating an issue](https://docs.github.com/en/github/managing-your-work-on-github/creating-an-issue) for discussion first. This lets us open a conversation, ensuring work is not duplicated unnecessarily and that the proposed PR is a fix or feature we're actively looking to add.\n\n## Bugs\n\nPlease [file an issue](https://docs.github.com/en/github/managing-your-work-on-github/creating-an-issue) for bugs, missing documentation, or unexpected behavior.\n\n## Feature Requests\n\nPlease file an issue to suggest new features. Vote on feature requests by adding\na 👍. This helps us prioritize what to work on.\n\n## Questions\n\nFor any questions and concerns related to using the package, feel free to email us via `chromogen.app@gmail.com`.\n<br><Br>\n## Chromogen V5.0 updates\n<br><hr>\n**GUI Overhaul**\n\n**Why?**\n  \n    *Hovering GUI blocked functionality of host app \n    *Recording/downloading interactivity was cumbersome and inflexible \n    *Suboptimal for CI/CD implementation Buttons not functional \n  \n**What?**\n  \n    *Discrete Collapsible IDE that allows for real-time observation & manual interactivity of generated tests \n  \n**Next steps:**\n  \n    *Recording button functionality\n  <br><Br><br><hr>\n  \n **Real-time feed rendering**\n\n**Why?**\n  \n    *Generated tests were only accessible as a monolith of text, preventing isolation of individual components’ tests\n  \n  **What?**\n  \n    *IDE updates in real-time as changes of state are recorded \n  \n  **Next steps:**\n  \n    *Test categorization. \n    *Filter groups of tests by: \n\n    *Initialization vs ΔState Action \n\n    *Description \n  \n    *and allow user to select which filter to apply to displayed generated tests.\n<br><Br><br><hr>\n   **CI/CD overhaul**\n\n**Why?**\n  \n    *Travis implementation not functional\n  \n  **What?**\n  \n    *Re-implemented CI/CD with Jenkins\n  \n  <br><Br><br><hr>\n\n  \n **Additional Next Steps**\n\n **Add functionality for Zustand multi-store rendering & Asynchronous state**\n\t\n**Docker containerization**\n  \n  **Why?**\n  \n    *V 4.0 presented inconsistencies when accessed from different local machines. \n\tThis hindered team workflow both with development and production-use\n  \n  **What?**\n  \n    *Containerization of app ensures homogenous, improved User/Dev experience\n\n <br> <br> <br> <hr>\n## Core Team\n\n<br>\n\n<table>\n  <tr align=\"center\">\n   <td align=\"center\"><a href=\"https://github.com/sirbrachthepale\"><img src=\"https://ca.slack-edge.com/T047AGRDFG8-U04EFS600F2-6758e04a3dcc-512\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brach Burdick</b></sub></a></td>\n    <td align=\"center\"><a href=\"https://github.com/dnvt\"><img src=\"https://avatars.githubusercontent.com/u/60344684?s=400&u=7fa22ae1486df42eaf172c3f08941416603387c0&v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Francois Denavaut</b></sub></a></td>\n     <td align=\"center\"><a href=\"https://github.com/maggiekwan\"><img src=\"https://ca.slack-edge.com/T047AGRDFG8-U046ZLFULCC-533eb79ef8c7-512\" width=\"100px;\" alt=\"\"/><br /><sub><b>Maggie Kwan</b></sub></a></td>\n      <td align=\"center\"><a href=\"https://github.com/Lawliang\"><img src=\"https://ca.slack-edge.com/T047AGRDFG8-U04CQEBF85B-267e9eba74d2-512\" width=\"100px;\" alt=\"\"/><br /><sub><b>Lawrence Liang</b></sub></a></td>\n    <td align=\"center\"><a href=\"https://github.com/michellebholland\"><img src=\"https://avatars3.githubusercontent.com/u/64747593\" width=\"100px;\" alt=\"\"/><br /><sub><b>Michelle Holland</b></sub></a></td>\n    <!-- SPACE -->\n    <td align=\"center\"><a href=\"https://github.com/andywang23\"><img src=\"https://avatars1.githubusercontent.com/u/64433815\" width=\"100px;\" alt=\"\"/><br /><sub><b>Andy Wang</b></sub></a></td>\n    <!-- SPACE -->\n    <td align=\"center\"><a href=\"https://github.com/connorrose\"><img src=\"https://avatars1.githubusercontent.com/u/42079810\" width=\"100px;\" alt=\"\"/><br /><sub><b>Connor Rose Delisle</b></sub></a></td>\n    <!-- SPACE -->\n    <td align=\"center\"><a href=\"https://github.com/chenchingk\"><img src=\"https://avatars0.githubusercontent.com/u/40308081\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jim Chen</b></sub></a></td>\n   <!-- SPACE -->\n  <td align=\"center\"><a href=\"https://github.com/amyy98\"><img src=\"https://avatars.githubusercontent.com/u/68040348?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Amy Yee</b></sub></a></td>\n     <!-- SPACE -->\n      <td align=\"center\"><a href=\"https://github.com/wlstjs\"><img src=\"https://avatars1.githubusercontent.com/u/68680285?s=400&u=5b89d376d4d27a77442b74dcfe1c9c4025ce6453&v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jinseon Shin</b></sub></a></td>\n   <!-- SPACE -->\n       <td align=\"center\"><a href=\"https://github.com/rtumel123\"><img src=\"https://i.postimg.cc/MGDTWMhQ/Ryan.jpg\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ryan Tumel</b></sub></a></td>\n       <!-- SPACE -->\n         <td align=\"center\"><a href=\"https://github.com/cgreer011\"><img src=\"https://i.postimg.cc/qMPgQdsz/cam.jpg\" width=\"100px;\" alt=\"\"/><br /><sub><b>Cameron Greer</b></sub></a></td>\n          <!-- SPACE -->\n         <td align=\"center\"><a href=\"https://github.com/nicholasjs\"><img src=\"https://avatars.githubusercontent.com/u/59386257?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Nicholas Shay</b></sub></a></td>\n</tr>\n<tr align=\"center\">\n    <!-- SPACE -->\n         <td align=\"center\"><a href=\"https://github.com/mp-04\"><img src=\"https://i.postimg.cc/nz6GjXXV/mp.jpg\" width=\"100px;\" alt=\"\"/><br /><sub><b>Marcellies Pettiford</b></sub></a></td>\n    <!-- SPACE -->\n         <td align=\"center\"><a href=\"https://github.com/smk53664\"><img src=\"https://i.postimg.cc/mrRkfN64/sung.jpg\" width=\"100px;\" alt=\"\"/><br /><sub><b>Sung Kim</b></sub></a></td>\n    <!-- SPACE -->\n         <td align=\"center\"><a href=\"https://github.com/lina4lee\"><img src=\"https://i.postimg.cc/bJwvdYhF/lina.jpg\" width=\"100px;\" alt=\"\"/><br /><sub><b>Lina Lee</b></sub></a></td>\n    <!-- SPACE -->\n         <td align=\"center\"><a href=\"https://github.com/ericaysoh\"><img src=\"https://i.postimg.cc/76tZzvPP/erica.jpg\" width=\"100px;\" alt=\"\"/><br /><sub><b>Erica Oh</b></sub></a></td>\n    <!-- SPACE -->\n         <td align=\"center\"><a href=\"https://github.com/dtalmaraz\"><img src=\"https://avatars.githubusercontent.com/u/94757231?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Dani Almaraz</b></sub></a></td>\n    <!-- SPACE -->\n    <tr align=\"center\">\n         <td align=\"center\"><a href=\"https://github.com/crgb0s\"><img src=\"https://i.postimg.cc/BbpvdSJD/craig.jpg\" width=\"100px;\" alt=\"\"/><br /><sub><b>Craig Boswell</b></sub></a></td>\n    <!-- SPACE -->\n         <td align=\"center\"><a href=\"https://github.com/Hali3030\"><img src=\"https://i.postimg.cc/xTj7Yf0C/hussein.jpg\" width=\"100px;\" alt=\"\"/><br /><sub><b>Hussein Ahmed</b></sub></a></td>\n    <!-- SPACE -->\n         <td align=\"center\"><a href=\"https://github.com/iannkila\"><img src=\"https://i.postimg.cc/rwcBD1C8/ian.jpg\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Kila</b></sub></a></td>\n    <!-- SPACE -->\n         <td align=\"center\"><a href=\"https://github.com/yuehaowong\"><img src=\"https://i.postimg.cc/T2JqRnwj/yuehao.jpg\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuehao Wong</b></sub></a></td>\n     </tr>            \n</tr>\n</table>\n<br><br>\n\n## LICENSE\n\nLogo crafted with [AdobeExpress](https://www.adobe.com/express/)\n\nREADME format adapted from [react-testing-library](https://github.com/testing-library/react-testing-library/blob/master/README.md) under [MIT license](https://github.com/testing-library/react-testing-library/blob/master/LICENSE).\n\nAll Chromogen source code is [MIT](./LICENSE) licensed.\n\nLastly, shoutout to [this repo](https://github.com/conorhastings/redux-test-recorder) for the original inspiration.\n"
  },
  {
    "path": "demo-todo/.babelrc",
    "content": "{ \n  \"presets\": [\n    [\"@babel/preset-env\"],\n    \"@babel/preset-react\"\n  ],\n  \"plugins\": [\n    //\"react-hot-loader/babel\"\n  ]\n} "
  },
  {
    "path": "demo-todo/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Michelle Holland\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "demo-todo/README.md",
    "content": "<div align=\"center\">\n\n# The official demo app for [Chromogen](https://github.com/oslabs-beta/Chromogen).\n\n![demo app interface](../assets/README-demo/demo-app.png)\n\n</div>\n\n## Selector Implementations\n  - Readonly:\n    1. displayed todo list items, based on filter selection (sort & active/complete)\n    2. stats (priority count and active/complete counts)\n    3. displayed todo list empty / non-empty\n  - Writeable:\n    1. \"all complete\" checkbox toggle\n    1. reset filter states\n  - Promise:\n    1. quote text\n  - Async / Await:\n    1. xkcd comic\n  - selectorFamily (_in progress_):\n    1. search bar"
  },
  {
    "path": "demo-todo/__tests__/initialTestTest.js",
    "content": "import { renderRecoilHook, act } from 'react-recoil-hooks-testing-library';\nimport { useRecoilValue, useRecoilState } from 'recoil';\nimport {\n  filteredTodoListState,\n  sortedTodoListState,\n  todoListSortedStats,\n  todoListStatsState,\n  filteredListContentState,\n  allCompleteState,\n  refreshFilterState,\n  searchBarSelectorFam,\n\n} from '../src/store/store';\nimport {\n  todoListState,\n  todoListFilterState,\n  todoListSortState,\n  quoteNumberState,\n  searchResultState,\n\n} from '../src/store/atoms';\n\n// Suppress 'Batcher' warnings from React / Recoil conflict\nconsole.error = jest.fn();\n\n// Hook to return atom/selector values and/or modifiers for react-recoil-hooks-testing-library\nconst useStoreHook = () => {\n  // atoms\n  const [todoListStateValue, settodoListState] = useRecoilState(todoListState);\n  const [todoListFilterStateValue, settodoListFilterState] = useRecoilState(todoListFilterState);\n  const [todoListSortStateValue, settodoListSortState] = useRecoilState(todoListSortState);\n  const [quoteNumberStateValue, setquoteNumberState] = useRecoilState(quoteNumberState);\n  const [searchResultStateValue, setsearchResultState] = useRecoilState(searchResultState);\n\n  // writeable selectors\n  const [allCompleteStateValue, setallCompleteState] = useRecoilState(allCompleteState);\n  const [refreshFilterStateValue, setrefreshFilterState] = useRecoilState(refreshFilterState);\n\n  // read-only selectors\n  const filteredTodoListStateValue = useRecoilValue(filteredTodoListState);\n  const sortedTodoListStateValue = useRecoilValue(sortedTodoListState);\n  const todoListSortedStatsValue = useRecoilValue(todoListSortedStats);\n  const todoListStatsStateValue = useRecoilValue(todoListStatsState);\n  const filteredListContentStateValue = useRecoilValue(filteredListContentState);\n\n  // atom families\n\n  // writeable selector families\n\n  // read-only selector families\n\n\n\n\n  return {\n    todoListStateValue,\n    settodoListState,\n    todoListFilterStateValue,\n    settodoListFilterState,\n    todoListSortStateValue,\n    settodoListSortState,\n    quoteNumberStateValue,\n    setquoteNumberState,\n    searchResultStateValue,\n    setsearchResultState,\n    allCompleteStateValue,\n    setallCompleteState,\n    refreshFilterStateValue,\n    setrefreshFilterState,\n    filteredTodoListStateValue,\n    sortedTodoListStateValue,\n    todoListSortedStatsValue,\n    todoListStatsStateValue,\n    filteredListContentStateValue,\n  };\n};\n\ndescribe('INITIAL RENDER', () => {\n  const { result } = renderRecoilHook(useStoreHook);\n\n  it('filteredTodoListState should initialize correctly', () => {\n    expect(result.current.filteredTodoListStateValue).toStrictEqual([]);\n  });\n\n  it('sortedTodoListState should initialize correctly', () => {\n    expect(result.current.sortedTodoListStateValue).toStrictEqual([]);\n  });\n\n  it('allCompleteState should initialize correctly', () => {\n    expect(result.current.allCompleteStateValue).toStrictEqual(true);\n  });\n\n  it('filteredListContentState should initialize correctly', () => {\n    expect(result.current.filteredListContentStateValue).toStrictEqual(false);\n  });\n\n  it('todoListSortedStats should initialize correctly', () => {\n    expect(result.current.todoListSortedStatsValue).toStrictEqual({});\n  });\n\n  it('todoListStatsState should initialize correctly', () => {\n    expect(result.current.todoListStatsStateValue).toStrictEqual({ \"totalNum\": 0, \"totalCompletedNum\": 0, \"totalUncompletedNum\": 0, \"percentCompleted\": 0 });\n  });\n\n\n});\n\ndescribe('SELECTORS', () => {\n  it('todoListSortedStats should properly derive state when todoListState updates', () => {\n    const { result } = renderRecoilHook(useStoreHook);\n\n    act(() => {\n      result.current.settodoListState([{ \"id\": 1, \"text\": \"tennis\", \"priority\": \"low\", \"isComplete\": false }]);\n\n      result.current.settodoListFilterState(\"Show All\");\n\n      result.current.settodoListSortState(false);\n\n      result.current.setquoteNumberState(23);\n\n      result.current.setsearchResultState({ \"all\": { \"searchTerm\": \"\", \"results\": [] }, \"high\": { \"searchTerm\": \"\", \"results\": [] }, \"medium\": { \"searchTerm\": \"\", \"results\": [] }, \"low\": { \"searchTerm\": \"\", \"results\": [] } });\n\n\n\n    });\n    expect(result.current.todoListSortedStatsValue).toStrictEqual({ \"low\": 1 });\n\n  });\n\n  it('todoListSortedStats should properly derive state when todoListState updates', () => {\n    const { result } = renderRecoilHook(useStoreHook);\n\n    act(() => {\n      result.current.settodoListState([{ \"id\": 1, \"text\": \"tennis\", \"priority\": \"low\", \"isComplete\": false }, { \"id\": 2, \"text\": \"chinese chicken\", \"priority\": \"low\", \"isComplete\": false }]);\n\n      result.current.settodoListFilterState(\"Show All\");\n\n      result.current.settodoListSortState(false);\n\n      result.current.setquoteNumberState(23);\n\n      result.current.setsearchResultState({ \"all\": { \"searchTerm\": \"\", \"results\": [] }, \"high\": { \"searchTerm\": \"\", \"results\": [] }, \"medium\": { \"searchTerm\": \"\", \"results\": [] }, \"low\": { \"searchTerm\": \"\", \"results\": [] } });\n\n\n\n    });\n    expect(result.current.todoListSortedStatsValue).toStrictEqual({ \"low\": 2 });\n\n  });\n\n});\n\ndescribe('SETTERS', () => {\n});"
  },
  {
    "path": "demo-todo/package.json",
    "content": "{\n  \"name\": \"chromogen-todo\",\n  \"version\": \"1.0.0\",\n  \"description\": \"demo todo app for Chromogen using React + Recoil\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"webpack-dev-server --open\",\n    \"test\": \"NODE_OPTIONS=--experimental-vm-modules npx jest --verbose\"\n  },\n  \"keywords\": [\n    \"react\",\n    \"recoil\",\n    \"chromogen\",\n    \"demo\",\n    \"example\",\n    \"todo\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/open-source-labs/Chromogen.git\"\n  },\n  \"contributors\": [\n    {\n      \"name\": \"Michelle Holland\",\n      \"url\": \"https://github.com/michellebholland/\"\n    },\n    {\n      \"name\": \"Jim Chen\",\n      \"url\": \"https://github.com/chenchingk\"\n    },\n    {\n      \"name\": \"Andy Wang\",\n      \"url\": \"https://github.com/andywang23\"\n    },\n    {\n      \"name\": \"Connor Rose Delisle\",\n      \"url\": \"https://github.com/connorrose\"\n    }\n  ],\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.11.1\",\n    \"@babel/plugin-transform-runtime\": \"^7.11.0\",\n    \"@babel/preset-env\": \"^7.19.1\",\n    \"@babel/preset-react\": \"^7.10.4\",\n    \"@testing-library/react\": \"^13.1.1\",\n    \"babel-loader\": \"^8.1.0\",\n    \"chromogen\": \"^4.0.4\",\n    \"css-loader\": \"^4.2.1\",\n    \"identity-obj-proxy\": \"^3.0.0\",\n    \"jest\": \"^26.4.2\",\n    \"style-loader\": \"^1.2.1\",\n    \"webpack\": \"^5.74.0\",\n    \"webpack-cli\": \"^3.3.12\",\n    \"webpack-dev-server\": \"^4.11.1\"\n  },\n  \"peerDependencies\": {\n    \"typescript\": \"^4.0.3\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.11.2\",\n    \"@emotion/react\": \"^11.10.4\",\n    \"@emotion/styled\": \"^11.10.4\",\n    \"@mui/icons-material\": \"^5.10.6\",\n    \"@mui/material\": \"^5.10.6\",\n    \"babel-jest\": \"^26.3.0\",\n    \"react\": \"^18.0.0\",\n    \"react-dom\": \"^18.0.0\",\n    \"react-recoil-hooks-testing-library\": \"^0.1.0\",\n    \"react-test-renderer\": \"^18.1.0\",\n    \"recoil\": \"0.7.2\",\n    \"typescript\": \"^4.0.3\"\n  },\n  \"jest\": {\n    \"moduleNameMapper\": {\n      \"\\\\.(css|less)$\": \"identity-obj-proxy\"\n    }\n  }\n}\n"
  },
  {
    "path": "demo-todo/src/components/App.jsx",
    "content": "import React from 'react';\nimport { RecoilRoot } from 'recoil';\nimport { ChromogenObserver } from 'chromogen';\nimport TodoList from './TodoList';\nimport * as selectors from '../store/store';\nimport * as atoms from '../store/atoms';\n\nconst App = () => (\n  <RecoilRoot>\n    <ChromogenObserver store={[selectors, atoms]} />\n    <TodoList />\n  </RecoilRoot>\n);\n\nexport default App;\n"
  },
  {
    "path": "demo-todo/src/components/Quotes.jsx",
    "content": "import React from 'react';\nimport { useRecoilValue, useSetRecoilState } from 'recoil';\nimport { quoteTextState, xkcdState } from '../store/store';\nimport { quoteNumberState } from '../store/atoms';\n\nconst Quotes = () => {\n  const setQuoteNumber = useSetRecoilState(quoteNumberState);\n  const quoteText = useRecoilValue(quoteTextState);\n\n  return (\n    <>\n      <div id=\"quoteContainer\">\n        <p>{quoteText}</p>\n        <button type=\"button\" onClick={() => setQuoteNumber(Math.floor(Math.random() * 1643))}>\n          New Quote\n        </button>\n      </div>\n    </>\n  );\n};\n\nexport default Quotes;\n"
  },
  {
    "path": "demo-todo/src/components/ReadOnlyTodoItem.jsx",
    "content": "import React from 'react';\nimport Checkbox from '@mui/material/Checkbox';\nimport '../styles/styles.css';\nimport { todoListState } from '../store/atoms';\nimport { useRecoilValue } from 'recoil';\n\nconst ReadOnlyTodoItem = ({ item }) => {\n  const checkBoxClasses = {\n    low: 'lowPriority',\n    medium: 'mediumPriority',\n    high: 'highPriority',\n  };\n\n  const todoList = useRecoilValue(todoListState);\n\n  return todoList.find((todo) => todo.id === item.id) ? (\n    <div className={checkBoxClasses[item.priority] || 'itemContainer'} id=\"todoItem\">\n      <input type=\"text\" value={item.text} readOnly />\n      <Checkbox\n        disableRipple\n        checked={todoList.find((todo) => todo.id === item.id).isComplete}\n        color=\"default\"\n        inputProps={{ 'aria-label': 'primary checkbox' }}\n        style={{ cursor: 'default' }}\n      />\n    </div>\n  ) : null;\n};\nexport default ReadOnlyTodoItem;\n"
  },
  {
    "path": "demo-todo/src/components/SearchBar.jsx",
    "content": "import React, { useState } from 'react';\nimport { useRecoilState } from 'recoil';\nimport { searchBarSelectorFam } from '../store/store';\n\nimport ReadOnlyTodoItem from './ReadOnlyTodoItem';\n\nconst SearchBar = () => {\n  const [searchFilter, setSearchFilter] = useState('all');\n  const [searchText, setSearchText] = useState('');\n  const [searchState, setSearchState] = useRecoilState(searchBarSelectorFam(searchFilter));\n\n  const onSearchTextChange = (e) => {\n    setSearchText(e.target.value);\n    setSearchState(e.target.value);\n  };\n  const onSelectChange = (e) => {\n    setSearchText('');\n    setSearchFilter(e.target.value);\n  };\n\n  return (\n    <div className=\"searchContainer\">\n      <input\n        className=\"searchField\"\n        placeholder=\"Search for a Todo\"\n        type=\"text\"\n        value={searchText || searchState.searchTerm}\n        onChange={onSearchTextChange}\n        onLoad={onSearchTextChange}\n      />\n      <select className=\"prioritySelect\" onChange={onSelectChange}>\n        <option value=\"all\">All Priorities</option>\n        <option value=\"high\">High Priority</option>\n        <option value=\"medium\">Medium Priority</option>\n        <option value=\"low\">Low Priority</option>\n      </select>\n      <div className=\"searchResults\">\n        {searchState.results.map((result, idx) => (\n          <ReadOnlyTodoItem key={idx} item={result} />\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport default SearchBar;\n"
  },
  {
    "path": "demo-todo/src/components/TodoItem.jsx",
    "content": "import React from 'react';\nimport { useRecoilState } from 'recoil';\nimport Checkbox from '@mui/material/Checkbox';\nimport { todoListState } from '../store/atoms';\nimport '../styles/styles.css';\n\nfunction replaceItemAtIndex(arr, index, newValue) {\n  return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];\n}\n\nfunction removeItemAtIndex(arr, index) {\n  return [...arr.slice(0, index), ...arr.slice(index + 1)];\n}\n\nconst TodoItem = ({ item }) => {\n  const [todoList, setTodoList] = useRecoilState(todoListState);\n  const index = todoList.findIndex((listItem) => listItem === item);\n\n  const editItemText = ({ target: { value } }) => {\n    const newList = replaceItemAtIndex(todoList, index, {\n      ...item,\n      text: value,\n    });\n    setTodoList(newList);\n  };\n  const toggleItemCompletion = () => {\n    const newList = replaceItemAtIndex(todoList, index, {\n      ...item,\n      isComplete: !item.isComplete,\n    });\n    setTodoList(newList);\n  };\n  const deleteItem = () => {\n    const newList = removeItemAtIndex(todoList, index);\n    setTodoList(newList);\n  };\n\n  const checkBoxClasses = {\n    low: 'lowPriority',\n    medium: 'mediumPriority',\n    high: 'highPriority',\n  };\n\n  return (\n    <div className={checkBoxClasses[item.priority] || 'itemContainer'} id=\"todoItem\">\n      <input type=\"text\" value={item.text} onChange={editItemText} />\n      <Checkbox\n        disableRipple\n        checked={item.isComplete}\n        color=\"default\"\n        inputProps={{ 'aria-label': 'primary checkbox' }}\n        onChange={toggleItemCompletion}\n      />\n      <button type=\"submit\" onClick={deleteItem}>\n        X\n      </button>\n    </div>\n  );\n};\nexport default TodoItem;\n"
  },
  {
    "path": "demo-todo/src/components/TodoItemCreator.jsx",
    "content": "/* eslint-disable react/jsx-props-no-spreading */\nimport React, { useState } from 'react';\nimport RadioGroup from '@mui/material/RadioGroup';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport FormControl from '@mui/material/FormControl';\nimport FormLabel from '@mui/material/FormLabel';\nimport Radio from '@mui/material/Radio';\nimport { useSetRecoilState } from 'recoil';\nimport { todoListState } from '../store/atoms';\n\n// utility for creating unique Id\nlet id = 0;\nconst getId = () => {\n  id += 1;\n  return id;\n};\n\nconst TodoItemCreator = () => {\n  const [inputValue, setInputValue] = useState('');\n  const [priorityValue, setPriorityValue] = useState('low');\n  const setTodoList = useSetRecoilState(todoListState);\n\n  const addItem = () => {\n    setTodoList((oldTodoList) => [\n      ...oldTodoList,\n      {\n        id: getId(),\n        text: inputValue,\n        priority: priorityValue,\n        isComplete: false,\n      },\n    ]);\n    setInputValue('');\n    setPriorityValue('low');\n  };\n\n  const onChange = ({ target: { value } }) => {\n    setInputValue(value);\n  };\n\n  const handleChange = (event) => {\n    setPriorityValue(event.target.value);\n  };\n\n  /* MUI Radio Button styles */\n  const GreenRadio = (props) => <Radio style={{ color: 'green' }} size=\"small\" {...props} />;\n\n  const YellowRadio = (props) => <Radio style={{ color: 'yellow' }} size=\"small\" {...props} />;\n\n  const RedRadio = (props) => <Radio style={{ color: 'red' }} size=\"small\" {...props} />;\n\n  return (\n    <div className=\"itemCreator\">\n      <input\n        className=\"inputText\"\n        placeholder=\"What needs to be done?\"\n        type=\"text\"\n        value={inputValue}\n        onChange={onChange}\n      />\n      <span id=\"radioContainer\">\n        <FormControl component=\"fieldset\">\n          <FormLabel color=\"secondary\" component=\"label\" />\n          <RadioGroup\n            row\n            aria-label=\"priority\"\n            name=\"priority1\"\n            value={priorityValue}\n            onChange={handleChange}\n          >\n            <FormControlLabel control={<RedRadio />} value=\"high\" />\n            <FormControlLabel control={<YellowRadio />} value=\"medium\" />\n            <FormControlLabel control={<GreenRadio />} value=\"low\" />\n          </RadioGroup>\n        </FormControl>\n      </span>\n\n      <button type=\"submit\" onClick={addItem}>\n        Add\n      </button>\n    </div>\n  );\n};\n\nexport default TodoItemCreator;\n"
  },
  {
    "path": "demo-todo/src/components/TodoList.jsx",
    "content": "import React from 'react';\nimport { useRecoilValue } from 'recoil';\nimport { sortedTodoListState } from '../store/store';\nimport TodoItem from './TodoItem';\nimport TodoItemCreator from './TodoItemCreator';\nimport TodoListFilters from './TodoListFilters';\nimport TodoQuickCheck from './TodoQuickCheck';\nimport Quotes from './Quotes';\nimport SearchBar from './SearchBar';\nimport '../styles/styles.css';\n\nconst TodoList = () => {\n  const todoList = useRecoilValue(sortedTodoListState);\n\n  return (\n    <div className=\"mainContainer\">\n      <div className=\"row quoteBox\">\n        <React.Suspense fallback={<small>Loading...</small>}>\n          <Quotes />\n        </React.Suspense>\n      </div>\n\n      <div className=\"row todosDisplayRow\">\n        <h1>Totally Todos!</h1>\n\n        <div className=\"todosContainer\">\n          <TodoQuickCheck />\n          <TodoItemCreator />\n          {todoList.map((todoItem) => (\n            <TodoItem key={todoItem.id} item={todoItem} />\n          ))}\n          <TodoListFilters />\n        </div>\n        <SearchBar />\n      </div>\n      <div className=\"row\" />\n    </div>\n  );\n};\n\nexport default TodoList;\n"
  },
  {
    "path": "demo-todo/src/components/TodoListFilters.jsx",
    "content": "import React, { useState } from 'react';\nimport SortIcon from '@mui/icons-material/Sort';\nimport EqualizerIcon from '@mui/icons-material/Equalizer';\nimport RefreshIcon from '@mui/icons-material/Refresh';\nimport { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil';\nimport { todoListStatsState, todoListSortedStats, refreshFilterState } from '../store/store';\nimport { todoListFilterState, todoListSortState } from '../store/atoms';\n\nconst TodoListFilters = () => {\n  const [filter, setFilter] = useRecoilState(todoListFilterState);\n  // selector - grabs totals for each category\n  const { high, medium, low } = useRecoilValue(todoListSortedStats);\n  // selector *writeable - resets sort and filter\n  const resetFilters = useResetRecoilState(refreshFilterState);\n  // selector - toggles sort on and off\n  const [sort, setSort] = useRecoilState(todoListSortState);\n  // toggle priority stats display\n  const [displayStats, setDisplayStats] = useState(false);\n  // selector - totals for each filter\n  const { totalNum, totalCompletedNum, totalUncompletedNum } = useRecoilValue(todoListStatsState);\n  const updateFilter = ({ target: { value } }) => setFilter(value);\n\n  const toggleSort = () => setSort(!sort);\n  const toggleDisplayStats = () => setDisplayStats(!displayStats);\n  const reset = () => {\n    setDisplayStats(false); // displayStats is local state\n    resetFilters();\n  };\n\n  const sortIconColor = {\n    true: 'sortedWhite',\n    false: 'aqua',\n  };\n\n  return (\n    <ul>\n      <button\n        className=\"filter-button\"\n        id=\"filterBtn1\"\n        style={{ color: filter === 'Show All' ? '#af6358' : 'whitesmoke' }}\n        type=\"submit\"\n        value=\"Show All\"\n        onClick={updateFilter}\n      >\n        All <span> {totalNum || ''}</span>\n      </button>\n      <button\n        className=\"filter-button\"\n        id=\"filterBtn2\"\n        style={{ color: filter === 'Show Uncompleted' ? '#af6358' : 'whitesmoke' }}\n        type=\"submit\"\n        value=\"Show Uncompleted\"\n        onClick={updateFilter}\n      >\n        Active <span>{totalUncompletedNum || ''}</span>\n      </button>\n      <button\n        className=\"filter-button\"\n        style={{ color: filter === 'Show Completed' ? '#af6358' : 'whitesmoke' }}\n        type=\"submit\"\n        value=\"Show Completed\"\n        onClick={updateFilter}\n      >\n        Complete <span>{totalCompletedNum || ''}</span>\n      </button>\n      <button id={sortIconColor[sort]} type=\"submit\" onClick={toggleSort}>\n        <SortIcon />\n      </button>\n\n      <button id=\"unsortedGray\" type=\"submit\" onClick={toggleDisplayStats}>\n        {displayStats && totalNum ? (\n          <span id=\"statsSpan\">\n            <span id=\"highSpan\">{high || 0}</span>\n            <span id=\"mediumSpan\">{medium || 0}</span>\n            <span id=\"lowSpan\">{low || 0}</span>\n          </span>\n        ) : (\n          <EqualizerIcon />\n        )}\n      </button>\n      <button id=\"unsortedGray\" type=\"submit\" onClick={reset}>\n        <RefreshIcon />\n      </button>\n    </ul>\n  );\n};\n\nexport default TodoListFilters;\n"
  },
  {
    "path": "demo-todo/src/components/TodoQuickCheck.jsx",
    "content": "import React from 'react';\nimport { useRecoilState, useRecoilValue } from 'recoil';\nimport Checkbox from '@mui/material/Checkbox';\nimport { allCompleteState, filteredListContentState } from '../store/store';\n\nconst TodoQuickCheck = () => {\n  const [allComplete, setAllComplete] = useRecoilState(allCompleteState);\n  const display = useRecoilValue(filteredListContentState);\n\n  return (\n    display && (\n      <div id=\"quickCheck\">\n        <Checkbox\n          disableRipple\n          checked={allComplete}\n          color=\"default\"\n          inputProps={{ 'aria-label': 'primary checkbox' }}\n          onChange={() => setAllComplete(!allComplete)}\n        />\n        all\n      </div>\n    )\n  );\n};\n\nexport default TodoQuickCheck;\n"
  },
  {
    "path": "demo-todo/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Palanquin:wght@300&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <title>Chromogen To-Do Demo</title>\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"./favicon.ico\" />\n  </head>\n  <body>\n    <div id=\"app\">\n      <script src=\"./bundle.js\"></script>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "demo-todo/src/index.js",
    "content": "/* eslint-disable react/jsx-filename-extension */\nimport React from 'react';\n//import { render } from 'react-dom';\nimport App from './components/App';\nimport { createRoot } from'react-dom/client';\n\nconst root = createRoot(document.getElementById('app'));\n\nroot.render(\n    <App />\n);\n"
  },
  {
    "path": "demo-todo/src/store/atoms.js",
    "content": "import { atom } from 'chromogen';\n\n/* ----- ATOMS ----- */\n\n// unsorted, unfiltered todo list\nconst todoListState = atom({\n  key: 'mismatchTodoList',\n  default: [], // array of objects - each object has id, text, isComplete, and priority props\n});\n\n// filter select\nconst todoListFilterState = atom({\n  key: 'todoListFilterState',\n  default: 'Show All',\n});\n\n// toggle sort\nconst todoListSortState = atom({\n  key: 'todoListSortState',\n  default: false,\n});\n\n// random number for fetching quote & comic\nconst quoteNumberState = atom({\n  key: 'quoteNumberState',\n  default: Math.floor(Math.random() * 1643),\n});\n\nconst searchResultState = atom({\n  key: 'searchResultState',\n  default: {\n    all: {\n      searchTerm: '',\n      results: [],\n    },\n    high: {\n      searchTerm: '',\n      results: [],\n    },\n    medium: {\n      searchTerm: '',\n      results: [],\n    },\n    low: {\n      searchTerm: '',\n      results: [],\n    },\n  },\n});\n\nexport {\n  todoListState,\n  todoListFilterState,\n  todoListSortState,\n  quoteNumberState,\n  searchResultState,\n};\n"
  },
  {
    "path": "demo-todo/src/store/store.js",
    "content": "import { selector, selectorFamily } from 'chromogen';\nimport {\n  todoListState,\n  todoListFilterState,\n  todoListSortState,\n  quoteNumberState,\n  searchResultState,\n} from './atoms';\n\n/* ----- SELECTORS ---- */\n\n// filtered todo list\nconst filteredTodoListState = selector({\n  key: 'filteredTodoListState',\n  get: ({ get }) => {\n    const filter = get(todoListFilterState);\n    const list = get(todoListState);\n\n    switch (filter) {\n      case 'Show Completed':\n        return list.filter((item) => item.isComplete);\n      case 'Show Uncompleted':\n        return list.filter((item) => !item.isComplete);\n      default:\n        return list;\n    }\n  },\n});\n\n// sorted todo list\nconst sortedTodoListState = selector({\n  key: 'mismatchSortedTodoList',\n  get: ({ get }) => {\n    const sort = get(todoListSortState);\n    const list = get(filteredTodoListState);\n    const high = list.filter((item) => item.priority === 'high');\n    const medium = list.filter((item) => item.priority === 'medium');\n    const low = list.filter((item) => item.priority === 'low');\n    return sort === false ? list : [...high, ...medium, ...low];\n  },\n});\n\n// priority stats\nconst todoListSortedStats = selector({\n  key: 'todoListSortedStats',\n  get: ({ get }) => {\n    const list = get(sortedTodoListState);\n    return list.reduce((acc, cv) => {\n      acc[cv.priority] = cv.priority in acc ? acc[cv.priority] + 1 : 1;\n      return acc;\n    }, {});\n  },\n});\n\n// completion (filter) stats\nconst todoListStatsState = selector({\n  key: 'todoListStatsState',\n  get: ({ get }) => {\n    const list = get(todoListState);\n    const totalNum = list.length;\n    const totalCompletedNum = list.filter((todo) => todo.isComplete).length;\n    const totalUncompletedNum = totalNum - totalCompletedNum;\n    const percentCompleted = totalNum === 0 ? 0 : totalCompletedNum / totalNum;\n    return {\n      totalNum,\n      totalCompletedNum,\n      totalUncompletedNum,\n      percentCompleted,\n    };\n  },\n});\n\n// is filtered list non-empty? (determines whether check-all displays)\nconst filteredListContentState = selector({\n  key: 'filteredListContentState',\n  get: ({ get }) => !!get(filteredTodoListState).length,\n});\n\n// WRITEABLE GET/SET SELECTOR - (un)check all filtered items\nconst allCompleteState = selector({\n  key: 'mismatchAllComplete',\n  // if any item in filteredList is not complete, allComplete is false\n  get: ({ get }) => !get(filteredTodoListState).some(({ isComplete }) => !isComplete),\n  set: ({ get, set }, newValue) => {\n    // update ONLY items in filtered list\n    const lookupTable = {};\n    get(todoListState).forEach((item) => {\n      lookupTable[item.id] = item;\n    });\n    get(filteredTodoListState).forEach((item) => {\n      lookupTable[item.id] = {\n        ...item,\n        isComplete: newValue,\n      };\n    });\n    set(todoListState, Object.values(lookupTable));\n  },\n});\n\n// WRITEABLE RESET SELECTOR - undo sort + filter\nconst refreshFilterState = selector({\n  key: 'refreshFilterState',\n  get: () => null,\n  set: ({ reset }) => {\n    reset(todoListSortState);\n    reset(todoListFilterState);\n  },\n});\n\n// PROMISE-BASED SELECTOR - fetch quote text\nconst quoteTextState = selector({\n  key: 'quoteTextState',\n  get: ({ get }) => {\n    const quoteNumber = get(quoteNumberState);\n    return fetch('https://type.fit/api/quotes')\n      .then((response) => response.json())\n      .then((data) => {\n        const quote = data[quoteNumber];\n        return `\"${quote.text}\"\\n\\t- ${quote.author || 'unknown'}`;\n      })\n      .catch((err) => {\n        console.error(err);\n        return 'No quote available';\n      });\n  },\n});\n\n// ASYNC SELECTOR - fetch comic img\n// const xkcdState = selector({\n//   key: 'xkcdState',\n//   get: async ({ get }) => {\n//     const quoteNumber = get(quoteNumberState);\n//     try {\n//       // Fetch much be proxied through cors-anywhere to test on localhost\n//       const response = await fetch(\n//         `https://cors-anywhere.herokuapp.com/http://xkcd.com/${quoteNumber}/info.0.json`,\n//       );\n//       const { img } = await response.json();\n//       return img;\n//     } catch (err) {\n//       // Fallback comic\n//       return 'https://imgs.xkcd.com/comics/api.png';\n//     }\n//   },\n// });\n\nconst searchBarSelectorFam = selectorFamily({\n  key: 'searchBarSelectorFam',\n  get:\n    (searchFilter) =>\n    ({ get }) =>\n      get(searchResultState)[searchFilter],\n  set:\n    (searchFilter) =>\n    ({ get, set }, searchTerm) => {\n      set(searchResultState, (prevState) => {\n        const newResults = get(todoListState).filter((todo) => {\n          if (searchTerm !== '' && todo.text.includes(searchTerm))\n            return searchFilter === 'all' ? true : todo.priority === searchFilter;\n          return false;\n        });\n        return { ...prevState, [searchFilter]: { searchTerm, results: newResults } };\n      });\n    },\n});\n\nexport {\n  filteredTodoListState,\n  filteredListContentState,\n  todoListStatsState,\n  allCompleteState,\n  sortedTodoListState,\n  todoListSortedStats,\n  refreshFilterState,\n  quoteTextState,\n  //xkcdState,\n  searchBarSelectorFam,\n};\n"
  },
  {
    "path": "demo-todo/src/styles/styles.css",
    "content": "/* -------------General Styles---------------- */\nhtml {\n  margin: 0;\n  background-color: rgb(48, 48, 48);\n  color: whitesmoke;\n  font-family: 'Palanquin', sans-serif;\n  overflow: hidden;\n}\n\nh1 {\n  text-align: center;\n  font-size: 2.3rem;\n  color: #af6358;\n  text-shadow: 1px 2px 2px rgba(250, 250, 250, 0.267);\n  letter-spacing: 3px;\n  font-style: italic;\n}\ninput,\nbutton,\nselect {\n  background-color: rgb(63, 63, 63);\n  border: 1px solid lightgray;\n  border: none;\n  padding: 20px;\n  border-radius: 4px;\n  color: whitesmoke;\n  font-size: 16px;\n  letter-spacing: 1px;\n}\n\n/* remove browser defaults */\nbutton:focus {\n  outline: none;\n}\ninput:focus {\n  outline: none;\n}\n/* ------------TodoList------------- */\n\n/* topmost container */\n.mainContainer {\n  display: grid;\n  height: 100vh;\n  width: 100vw;\n  grid-template-rows: 15fr 33fr 33fr;\n}\n\n/* overall row container for todo list display */\n.todosDisplayRow {\n  margin: 0 auto;\n  width: 700px;\n}\n\n/* container for entire list display */\n.todosContainer {\n  background-color: rgb(63, 63, 63);\n  box-shadow: 0px 0px 35px 20px rgba(10, 10, 10, 0.096);\n  border-radius: 5px;\n  padding: 5px 12px 0 12px;\n}\n\n/* -------------TodoItemCreator---------------- */\n\n.itemCreator button {\n  padding: 0px;\n}\n\ninput::placeholder {\n  font-style: italic;\n  letter-spacing: 1.5px;\n}\n\n.itemCreator input {\n  padding: 0 0 0 5%;\n  width: 70%;\n  height: 60px;\n}\n\n#radioContainer svg {\n  margin-top: 11px;\n  opacity: 0.7;\n}\n\n/* -------------TodoItem---------------- */\n\n.itemContainer,\n.lowPriority,\n.mediumPriority,\n.highPriority {\n  display: grid;\n  grid-template-columns: 79fr 14fr 7fr;\n  border-bottom: 1px solid rgba(245, 245, 245, 0.336);\n}\n\n/* dynamic checkbox color */\n.highPriority svg {\n  color: #ef5350;\n  opacity: 0.7;\n  margin-right: 28px;\n}\n.mediumPriority svg {\n  color: #ffee58;\n  opacity: 0.7;\n  margin-right: 28px;\n}\n.lowPriority svg {\n  color: #66bb6a;\n  opacity: 0.7;\n  margin-right: 28px;\n}\n\n#todoItem button {\n  margin-left: 7px;\n}\n\n/* -------------TodoListFilter---------------- */\n\nul {\n  display: grid;\n  grid-template-columns: 27fr 27fr 27fr 6fr 6fr 6fr;\n  margin: 0;\n  padding: 0px;\n  width: 100%;\n}\n\n.filter-button {\n  margin-top: 10px;\n  margin-bottom: 10px;\n  border-right: 1px solid rgba(143, 143, 143, 0.26);\n  padding: 10px 20px;\n  border-radius: 0px 0px 4px 4px;\n}\n\n/* dynamic sort icon color */\n#sortedWhite svg {\n  color: whitesmoke;\n}\n#unsortedGray svg {\n  color: rgba(245, 245, 245, 0.336);\n}\n#unsortedGray {\n  width: 64px;\n}\n\n#statsSpan {\n  display: grid;\n  grid-template-columns: 30fr 30fr 30fr;\n  align-items: center;\n}\n#highSpan {\n  color: #ef5350;\n  opacity: 0.7;\n  margin-right: 4px;\n}\n#mediumSpan {\n  color: #ffee58;\n  opacity: 0.7;\n  margin-right: 4px;\n}\n#lowSpan {\n  color: #66bb6a;\n  opacity: 0.7;\n}\n\n/* filter stats (number) */\nbutton span {\n  opacity: 0.6;\n}\n\n/* ----------QuoteBox---------- */\n\n#quoteContainer {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n}\n\n#quoteContainer p {\n  white-space: pre-wrap;\n}\n\n.quoteBox {\n  display: flex;\n  flex-direction: row;\n  justify-content: space-between;\n  background-color: rgb(63, 63, 63);\n  box-shadow: 0px 0px 35px 20px rgba(10, 10, 10, 0.096);\n  border-radius: 5px;\n  margin: auto;\n  padding: 12px;\n}\n\n.quoteBox img {\n  margin: 20px;\n  height: 150px;\n  width: 150px;\n}\n\n.quoteBox button {\n  width: 150px;\n}\n\n.quoteBox button:hover {\n  border: 1px solid whitesmoke;\n  cursor: pointer;\n}\n\n/* ---------- TodoQuickCheck ---------- */\n\n#quickCheck {\n  font-family: Arial, Helvetica, sans-serif;\n}\n\n#quickCheck svg {\n  color: #af6358;\n}\n\n/* ---------- Search ---------- */\n.searchContainer {\n  margin-top: 100px;\n  background-color: rgb(63, 63, 63);\n  box-shadow: 0px 0px 35px 20px rgba(10, 10, 10, 0.096);\n  border-radius: 5px;\n  padding: 5px 12px 12px 12px;\n  display: grid;\n  grid-template-columns: 70% 30%;\n}\n\n.searchField {\n  border-bottom: solid 1px whitesmoke;\n  border-radius: 0;\n}\n\n.prioritySelect {\n  grid-column-start: 2;\n  border-bottom: solid 1px whitesmoke;\n  border-radius: 0;\n}\n\n.searchResults {\n  grid-column-start: span 3;\n}\n"
  },
  {
    "path": "demo-todo/webpack.config.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  entry: path.resolve(__dirname, './src/index.js'),\n  output: {\n    filename: 'bundle.js',\n  },\n  devServer: {\n    contentBase: path.resolve(__dirname, './src'),\n    historyApiFallback: true,\n  },\n  mode: process.env.NODE_ENV,\n  module: {\n    rules: [\n      {\n        test: /\\.(js|jsx)$/,\n        exclude: /node_modules/,\n        loader: 'babel-loader',\n        options: {\n          presets: ['@babel/preset-env', '@babel/preset-react'],\n          plugins: ['@babel/transform-runtime'],\n        },\n      },\n      {\n        test: /\\.css$/,\n        use: [\n          {\n            loader: 'style-loader',\n          },\n          {\n            loader: 'css-loader',\n          },\n        ],\n      },\n    ],\n  },\n  resolve: {\n    extensions: ['.js', '.jsx'],\n  },\n};\n"
  },
  {
    "path": "demo-zustand-todo/.babelrc",
    "content": "{ \n  \"presets\": [\n    [\"@babel/preset-env\"],\n    \"@babel/preset-react\"\n  ],\n  \"plugins\": [\n    //\"react-hot-loader/babel\"\n  ]\n} "
  },
  {
    "path": "demo-zustand-todo/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Michelle Holland\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "demo-zustand-todo/__tests__/sampleTest.js",
    "content": "import { renderHook, act } from '@testing-library/react';\nimport useStore from '../src/store/store';\n\ndescribe('INITIAL RENDER', () => {\n  const { result } = renderHook(useStore);\n\n  it('todoListState should initialize correctly', () => {\n    expect(result.current.todoListState).toStrictEqual([]);\n  });\n\n  it('todoListFilterState should initialize correctly', () => {\n    expect(result.current.todoListFilterState).toStrictEqual('Show All');\n  });\n\n  it('todoListSortState should initialize correctly', () => {\n    expect(result.current.todoListSortState).toStrictEqual(false);\n  });\n\n  it('quoteText should initialize correctly', () => {\n    expect(result.current.quoteText).toStrictEqual('');\n  });\n\n  it('quoteNumber should initialize correctly', () => {\n    expect(result.current.quoteNumber).toStrictEqual(0);\n  });\n\n  it('checkBox should initialize correctly', () => {\n    expect(result.current.checkBox).toStrictEqual(false);\n  });\n\n  it('searchResultState should initialize correctly', () => {\n    expect(result.current.searchResultState).toStrictEqual({\n      all: { searchTerm: '', results: [] },\n      high: { searchTerm: '', results: [] },\n      medium: { searchTerm: '', results: [] },\n      low: { searchTerm: '', results: [] },\n    });\n  });\n});\n\ndescribe('STATE CHANGES', () => {\n  const { result } = renderHook(useStore);\n\n  it('checkBox & quoteText & todoListState should update correctly', () => {\n    const { result } = renderHook(useStore);\n\n    act(() => {\n      result.current.setCheckBox();\n      result.current.setCheckBox();\n      result.current.changeQuoteText(\n        '\"Your ability to learn faster than your competition is your only sustainable competitive advantage.\"\\n\\t- Arie de Gues',\n      );\n      result.current.addTodoListItem({ id: 2, text: 'tennis', priority: 'low', isComplete: false });\n    });\n\n    expect(result.current.checkBox).toStrictEqual(true);\n    expect(result.current.quoteText).toStrictEqual(\n      '\"Your ability to learn faster than your competition is your only sustainable competitive advantage.\"\\n\\t- Arie de Gues',\n    );\n    expect(result.current.todoListState).toStrictEqual([\n      { id: 2, text: 'tennis', priority: 'low', isComplete: false },\n    ]);\n  });\n  it('checkBox & todoListState should update correctly', () => {\n    const { result } = renderHook(useStore);\n\n    act(() => {\n      result.current.setCheckBox();\n      result.current.setCheckBox();\n      result.current.addTodoListItem({ id: 3, text: 'hockey', priority: 'low', isComplete: false });\n      result.current.setCheckBox();\n    });\n\n    expect(result.current.checkBox).toStrictEqual(false);\n    expect(result.current.todoListState).toStrictEqual([\n      { id: 2, text: 'tennis', priority: 'low', isComplete: false },\n      { id: 3, text: 'hockey', priority: 'low', isComplete: false },\n    ]);\n  });\n  it('todoListState should update correctly', () => {\n    const { result } = renderHook(useStore);\n\n    act(() => {\n      result.current.addTodoListItem({ id: 4, text: 'hocka', priority: 'low', isComplete: false });\n      result.current.setCheckBox();\n    });\n\n    expect(result.current.todoListState).toStrictEqual([\n      { id: 2, text: 'tennis', priority: 'low', isComplete: false },\n      { id: 3, text: 'hockey', priority: 'low', isComplete: false },\n      { id: 4, text: 'hocka', priority: 'low', isComplete: false },\n    ]);\n  });\n  it('todoListState & searchResultState should update correctly', () => {\n    const { result } = renderHook(useStore);\n\n    act(() => {\n      result.current.addTodoListItem({ id: 5, text: 'canoe', priority: 'low', isComplete: false });\n      result.current.setCheckBox();\n      result.current.setSearchState('c', 'all');\n    });\n\n    expect(result.current.todoListState).toStrictEqual([\n      { id: 2, text: 'tennis', priority: 'low', isComplete: false },\n      { id: 3, text: 'hockey', priority: 'low', isComplete: false },\n      { id: 4, text: 'hocka', priority: 'low', isComplete: false },\n      { id: 5, text: 'canoe', priority: 'low', isComplete: false },\n    ]);\n    expect(result.current.searchResultState).toStrictEqual({\n      all: {\n        searchTerm: 'c',\n        results: [\n          { id: 3, text: 'hockey', priority: 'low', isComplete: false },\n          { id: 4, text: 'hocka', priority: 'low', isComplete: false },\n          { id: 5, text: 'canoe', priority: 'low', isComplete: false },\n        ],\n      },\n      high: { searchTerm: '', results: [] },\n      medium: { searchTerm: '', results: [] },\n      low: { searchTerm: '', results: [] },\n    });\n  });\n  it('searchResultState should update correctly', () => {\n    const { result } = renderHook(useStore);\n\n    act(() => {\n      result.current.setSearchState('ca', 'all');\n    });\n\n    expect(result.current.searchResultState).toStrictEqual({\n      all: {\n        searchTerm: 'ca',\n        results: [{ id: 5, text: 'canoe', priority: 'low', isComplete: false }],\n      },\n      high: { searchTerm: '', results: [] },\n      medium: { searchTerm: '', results: [] },\n      low: { searchTerm: '', results: [] },\n    });\n  });\n  it('searchResultState should update correctly', () => {\n    const { result } = renderHook(useStore);\n\n    act(() => {\n      result.current.setSearchState('can', 'all');\n    });\n\n    expect(result.current.searchResultState).toStrictEqual({\n      all: {\n        searchTerm: 'can',\n        results: [{ id: 5, text: 'canoe', priority: 'low', isComplete: false }],\n      },\n      high: { searchTerm: '', results: [] },\n      medium: { searchTerm: '', results: [] },\n      low: { searchTerm: '', results: [] },\n    });\n  });\n  it('searchResultState should update correctly', () => {\n    const { result } = renderHook(useStore);\n\n    act(() => {\n      result.current.setSearchState('cano', 'all');\n    });\n\n    expect(result.current.searchResultState).toStrictEqual({\n      all: {\n        searchTerm: 'cano',\n        results: [{ id: 5, text: 'canoe', priority: 'low', isComplete: false }],\n      },\n      high: { searchTerm: '', results: [] },\n      medium: { searchTerm: '', results: [] },\n      low: { searchTerm: '', results: [] },\n    });\n  });\n});\n"
  },
  {
    "path": "demo-zustand-todo/package.json",
    "content": "{\n  \"name\": \"chromogen-todo\",\n  \"version\": \"1.0.2\",\n  \"description\": \"demo todo app for Chromogen using React + Recoil\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"webpack-dev-server --open\",\n    \"test\": \"jest --verbose\",\n    \"update\": \" npm run uninstall && npm run install && npm run start\",\n    \"uninstall\": \"npm uninstall chromogen\",\n    \"install\": \"npm install ../package\",\n    \"buildPackage\": \"tsc\",\n    \"tarballUpdate\": \"npm --prefix ../package run build && npm pack ../package && npm uninstall chromogen && npm install ./chromogen-5.0.1.tgz && npm start\"\n  },\n  \"keywords\": [\n    \"react\",\n    \"recoil\",\n    \"chromogen\",\n    \"demo\",\n    \"example\",\n    \"todo\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/open-source-labs/Chromogen.git\"\n  },\n  \"contributors\": [\n    {\n      \"name\": \"Brach Burdick\",\n      \"url\": \"https://github.com/sirbrachthepale/\"\n    },\n    {\n      \"name\": \"Francois Denavaut\",\n      \"url\": \"https://github.com/dnvt/\"\n    },\n    {\n      \"name\": \"Maggie Kwan\",\n      \"url\": \"https://github.com/maggiekwan/\"\n    },\n    {\n      \"name\": \"Lawrence Liang\",\n      \"url\": \"https://github.com/Lawliang/\"\n    },\n    {\n      \"name\": \"Michelle Holland\",\n      \"url\": \"https://github.com/michellebholland/\"\n    },\n    {\n      \"name\": \"Jim Chen\",\n      \"url\": \"https://github.com/chenchingk\"\n    },\n    {\n      \"name\": \"Andy Wang\",\n      \"url\": \"https://github.com/andywang23\"\n    },\n    {\n      \"name\": \"Connor Rose Delisle\",\n      \"url\": \"https://github.com/connorrose\"\n    }\n  ],\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.11.1\",\n    \"@babel/plugin-transform-runtime\": \"^7.11.0\",\n    \"@babel/preset-env\": \"^7.11.0\",\n    \"@babel/preset-react\": \"^7.10.4\",\n    \"@testing-library/react\": \"^13.1.1\",\n    \"babel-loader\": \"^8.1.0\",\n    \"css-loader\": \"^4.2.1\",\n    \"identity-obj-proxy\": \"^3.0.0\",\n    \"jest\": \"^26.4.2\",\n    \"prettier\": \"^2.7.1\",\n    \"style-loader\": \"^1.2.1\",\n    \"webpack\": \"^4.44.1\",\n    \"webpack-cli\": \"^3.3.12\",\n    \"webpack-dev-server\": \"^3.11.0\"\n  },\n  \"peerDependencies\": {\n    \"typescript\": \"^4.0.3\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.11.2\",\n    \"@emotion/react\": \"^11.10.5\",\n    \"@emotion/styled\": \"^11.10.4\",\n    \"@mui/icons-material\": \"^5.10.6\",\n    \"@mui/material\": \"^5.10.6\",\n    \"babel-jest\": \"^26.3.0\",\n    \"chromogen\": \"file:chromogen-4.0.4.tgz\",\n    \"file-loader\": \"^6.2.0\",\n    \"react\": \"^18.0.0\",\n    \"react-dom\": \"^18.0.0\",\n    \"react-test-renderer\": \"^18.1.0\",\n    \"recoil\": \"0.7.5\",\n    \"typescript\": \"^4.0.3\",\n    \"url-loader\": \"^4.1.1\",\n    \"zustand\": \"^4.1.1\"\n  },\n  \"jest\": {\n    \"moduleNameMapper\": {\n      \"\\\\.(css|less)$\": \"identity-obj-proxy\"\n    }\n  }\n}\n"
  },
  {
    "path": "demo-zustand-todo/src/components/App.jsx",
    "content": "import React from 'react';\nimport { ChromogenZustandObserver } from 'chromogen';\nimport TodoList from './TodoList';\nimport '../styles/styles.css';\n\nconst App = () => (\n  <>\n    <ChromogenZustandObserver>\n      <TodoList />\n    </ChromogenZustandObserver>\n  </>\n);\n\nexport default App;\n"
  },
  {
    "path": "demo-zustand-todo/src/components/Quotes.jsx",
    "content": "import React from 'react';\nimport shallow from 'zustand/shallow';\nimport useToDoStore from '../store/store';\nimport { useEffect } from 'react';\n\nconst selector = (state) => ({\n  changeQuoteText: state.changeQuoteText,\n  quoteText: state.quoteText,\n});\n\nconst Quotes = () => {\n  const { changeQuoteText, quoteText } = useToDoStore(selector, shallow);\n\n  const fetchMe = () => {\n    let randomNum = Math.floor(Math.random() * 1643);\n\n    fetch('https://type.fit/api/quotes')\n      .then((response) => response.json())\n      .then((data) => {\n        const quote = data[randomNum];\n        changeQuoteText(`\"${quote.text}\"\\n\\t- ${quote.author || 'unknown'}`);\n      })\n      .catch((err) => {\n        console.error(err);\n        return 'No quote available';\n      });\n  };\n\n  useEffect(() => fetchMe(), []);\n\n  return (\n    <>\n      <div id=\"quoteContainer\">\n        <p>{quoteText}</p>\n        <a onClick={() => fetchMe()}>New Quote</a>\n      </div>\n    </>\n  );\n};\n\nexport default Quotes;\n"
  },
  {
    "path": "demo-zustand-todo/src/components/ReadOnlyTodoItem.jsx",
    "content": "import React from 'react';\nimport Checkbox from '@mui/material/Checkbox';\nimport '../styles/styles.css';\nimport useToDoStore from '../store/store';\n\nconst ReadOnlyTodoItem = ({ item }) => {\n  const checkBoxClasses = {\n    low: 'lowPriority',\n    medium: 'mediumPriority',\n    high: 'highPriority',\n  };\n\n  const todoList = useToDoStore((state) => state.todoListState);\n\n  return todoList.find((todo) => todo.id === item.id) ? (\n    <div className={checkBoxClasses[item.priority] || 'itemContainer'} id=\"todoItem\">\n      <input type=\"text\" value={item.text} readOnly />\n      <Checkbox\n        disableRipple\n        checked={todoList.find((todo) => todo.id === item.id).isComplete}\n        color=\"default\"\n        inputProps={{ 'aria-label': 'primary checkbox' }}\n        style={{ cursor: 'default' }}\n      />\n    </div>\n  ) : null;\n};\nexport default ReadOnlyTodoItem;\n"
  },
  {
    "path": "demo-zustand-todo/src/components/SearchBar.jsx",
    "content": "import React, { useState } from 'react';\nimport useToDoStore from '../store/store';\nimport shallow from 'zustand/shallow';\n\nimport ReadOnlyTodoItem from './ReadOnlyTodoItem';\n\nconst selector = (state) => ({\n  searchResultState: state.searchResultState,\n  setSearchState: state.setSearchState,\n});\n\nconst SearchBar = () => {\n  const [searchFilter, setSearchFilter] = useState('all');\n  const [searchText, setSearchText] = useState('');\n  const { searchResultState, setSearchState } = useToDoStore(selector, shallow);\n  const searchResults = searchResultState[searchFilter];\n\n  const onSearchTextChange = (e) => {\n    setSearchText(e.target.value);\n    setSearchState(e.target.value, searchFilter);\n  };\n  const onSelectChange = (e) => {\n    setSearchText('');\n    setSearchFilter(e.target.value);\n  };\n\n  return (\n    <div className=\"searchContainer\">\n      <input\n        className=\"searchField\"\n        placeholder=\"Search for a Todo\"\n        type=\"text\"\n        value={searchText || searchResults.searchTerm}\n        onChange={onSearchTextChange}\n        onLoad={onSearchTextChange}\n      />\n      <select\n        style={{ WebkitAppearance: 'none' }}\n        className=\"prioritySelect\"\n        onChange={onSelectChange}\n      >\n        <option value=\"all\">All Priorities</option>\n        <option value=\"high\">High Priority</option>\n        <option value=\"medium\">Medium Priority</option>\n        <option value=\"low\">Low Priority</option>\n      </select>\n      <div className=\"searchResults\">\n        {searchResults.results.map((result, idx) => (\n          <ReadOnlyTodoItem key={idx} item={result} />\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport default SearchBar;\n"
  },
  {
    "path": "demo-zustand-todo/src/components/TodoItem.jsx",
    "content": "import React from 'react';\nimport Checkbox from '@mui/material/Checkbox';\nimport '../styles/styles.css';\nimport shallow from 'zustand/shallow';\nimport useToDoStore from '../store/store';\n\nconst selector = (state) => ({\n  todoListState: state.todoListState,\n  deleteTodoListItem: state.deleteTodoListItem,\n  editItemText: state.editItemText,\n  toggleItemCompletion: state.toggleItemCompletion,\n});\n\nconst TodoItem = ({ item }) => {\n  const { deleteTodoListItem, editItemText, toggleItemCompletion } = useToDoStore(\n    selector,\n    shallow,\n  );\n\n  const checkBoxClasses = {\n    low: 'lowPriority',\n    medium: 'mediumPriority',\n    high: 'highPriority',\n  };\n\n  return (\n    <div className={checkBoxClasses[item.priority] || 'itemContainer'} id=\"todoItem\">\n      <input\n        type=\"text\"\n        value={item.text}\n        onChange={(e) => editItemText(e.target.value, item.id)}\n      />\n      <Checkbox\n        disableRipple\n        checked={item.isComplete}\n        color=\"default\"\n        inputProps={{ 'aria-label': 'primary checkbox' }}\n        onClick={() => toggleItemCompletion(item.id)}\n      />\n      <button type=\"submit\" onClick={() => deleteTodoListItem(item.id)}>\n        X\n      </button>\n    </div>\n  );\n};\nexport default TodoItem;\n"
  },
  {
    "path": "demo-zustand-todo/src/components/TodoItemCreator.jsx",
    "content": "/* eslint-disable react/jsx-props-no-spreading */\nimport React, { useState } from 'react';\nimport RadioGroup from '@mui/material/RadioGroup';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport FormControl from '@mui/material/FormControl';\nimport FormLabel from '@mui/material/FormLabel';\nimport Radio from '@mui/material/Radio';\nimport useToDoStore from '../store/store';\n\nconst selector = (state) => state.addTodoListItem;\n\n// utility for creating unique Id\nlet id = 1;\nconst getId = () => {\n  id += 1;\n  return id;\n};\n\nconst TodoItemCreator = () => {\n  const [inputValue, setInputValue] = useState('');\n  const [priorityValue, setPriorityValue] = useState('low');\n  const addTodoListItem = useToDoStore(selector);\n\n  const addItem = () => {\n    addTodoListItem({\n      id: getId(),\n      text: inputValue,\n      priority: priorityValue,\n      isComplete: false,\n    });\n    setInputValue('');\n    setPriorityValue('low');\n  };\n\n  const onChange = ({ target: { value } }) => {\n    setInputValue(value);\n  };\n\n  const handleChange = (event) => {\n    setPriorityValue(event.target.value);\n  };\n\n  /* MUI Radio Button styles */\n  const GreenRadio = (props) => <Radio style={{ color: 'green' }} size=\"small\" {...props} />;\n\n  const YellowRadio = (props) => <Radio style={{ color: 'yellow' }} size=\"small\" {...props} />;\n\n  const RedRadio = (props) => <Radio style={{ color: 'red' }} size=\"small\" {...props} />;\n\n  return (\n    <div className=\"itemCreator\">\n      <input\n        className=\"inputText\"\n        placeholder=\"What needs to be done?\"\n        type=\"text\"\n        value={inputValue}\n        onChange={onChange}\n      />\n      <span id=\"radioContainer\">\n        <FormControl component=\"fieldset\">\n          <FormLabel color=\"secondary\" component=\"label\" />\n          <RadioGroup\n            row\n            aria-label=\"priority\"\n            name=\"priority1\"\n            value={priorityValue}\n            onChange={handleChange}\n          >\n            <FormControlLabel control={<RedRadio />} value=\"high\" />\n            <FormControlLabel control={<YellowRadio />} value=\"medium\" />\n            <FormControlLabel control={<GreenRadio />} value=\"low\" />\n          </RadioGroup>\n        </FormControl>\n      </span>\n\n      <button type=\"submit\" onClick={addItem}>\n        Add\n      </button>\n    </div>\n  );\n};\n\nexport default TodoItemCreator;\n"
  },
  {
    "path": "demo-zustand-todo/src/components/TodoList.jsx",
    "content": "import React from 'react';\nimport TodoItem from './TodoItem';\nimport TodoItemCreator from './TodoItemCreator';\nimport TodoListFilters from './TodoListFilters';\nimport TodoQuickCheck from './TodoQuickCheck';\nimport Quotes from './Quotes';\nimport SearchBar from './SearchBar';\nimport '../styles/styles.css';\nimport shallow from 'zustand/shallow';\nimport useToDoStore from '../store/store';\n\nconst selector = (state) => ({\n  todoListState: state.todoListState,\n  todoListFilterState: state.todoListFilterState,\n  todoListSortState: state.todoListSortState,\n});\n\nconst filterList = (list, filter) => {\n  switch (filter) {\n    case 'Show Completed':\n      return list.filter((item) => item.isComplete);\n    case 'Show Uncompleted':\n      return list.filter((item) => !item.isComplete);\n    default:\n      return list;\n  }\n};\n\nconst sortList = (list, sortingMethod) => {\n  if (!sortingMethod) return list;\n  const high = list.filter((item) => item.priority === 'high');\n  const medium = list.filter((item) => item.priority === 'medium');\n  const low = list.filter((item) => item.priority === 'low');\n  return [...high, ...medium, ...low];\n};\n\nconst TodoList = () => {\n  const { todoListState, todoListFilterState, todoListSortState } = useToDoStore(selector, shallow);\n  const todoList = sortList(filterList(todoListState, todoListFilterState), todoListSortState);\n\n  return (\n    <div className=\"mainContainer\">\n      <div className=\"wrapper\">\n        <center>\n          <img\n            id=\"newChromogenLogo\"\n            src=\"https://i.postimg.cc/sgXkWQmt/Chromogen-1.png\"\n            alt=\"this is supposed to be our logo\"\n          />\n        </center>\n        <div className=\"quoteBox\">\n          <React.Suspense fallback={<small>Loading...</small>}>\n            <Quotes />\n          </React.Suspense>\n        </div>\n        <div className=\"todosDisplayRow\">\n          <h1>To-Do List</h1>\n          <div className=\"todosContainer\">\n            <TodoQuickCheck />\n            <TodoItemCreator />\n            {todoList.map((todoItem) => (\n              <TodoItem key={todoItem.id} item={todoItem} />\n            ))}\n            <TodoListFilters />\n          </div>\n        </div>\n        <SearchBar />\n        <div className=\"row\" />\n      </div>\n    </div>\n  );\n};\n\nexport default TodoList;\n"
  },
  {
    "path": "demo-zustand-todo/src/components/TodoListFilters.jsx",
    "content": "import React, { useState } from 'react';\nimport SortIcon from '@mui/icons-material/Sort';\nimport EqualizerIcon from '@mui/icons-material/Equalizer';\nimport RefreshIcon from '@mui/icons-material/Refresh';\nimport useToDoStore from '../store/store';\nimport shallow from 'zustand/shallow';\n\nconst selector = (state) => ({\n  todoListFilterState: state.todoListFilterState,\n  todoListState: state.todoListState,\n  resetFiltersAndSorted: state.resetFiltersAndSorted,\n  todoListSortState: state.todoListSortState,\n  toggleSort: state.toggleSort,\n  setFilter: state.setFilter,\n});\n\nconst TodoListFilters = () => {\n  const {\n    todoListFilterState,\n    todoListState,\n    resetFiltersAndSorted,\n    todoListSortState,\n    toggleSort,\n    setFilter,\n  } = useToDoStore(selector, shallow);\n\n  // // selector - grabs totals for each category\n  const { high, medium, low } = todoListState.reduce((acc, cur) => {\n    acc[cur.priority] = (acc[cur.priority] ?? 0) + 1;\n    return acc;\n  }, {});\n\n  // // toggle priority stats display\n  const [displayStats, setDisplayStats] = useState(false);\n\n  // // selector - totals for each filter\n  const totalNum = todoListState.length;\n  const totalCompletedNum = todoListState.filter((todo) => todo.isComplete).length;\n  const totalUncompletedNum = todoListState.filter((todo) => !todo.isComplete).length;\n\n  const updateFilter = ({ target: { value } }) => setFilter(value);\n\n  const toggleDisplayStats = () => setDisplayStats(!displayStats);\n  const reset = () => {\n    setDisplayStats(false); // displayStats is local state\n    resetFiltersAndSorted();\n  };\n\n  const sortIconColor = {\n    true: 'sortedWhite',\n    false: 'unsortedGray',\n  };\n\n  return (\n    <ul>\n      <button\n        className=\"filter-button\"\n        id=\"filterBtn1\"\n        style={{ color: todoListFilterState === 'Show All' ? '#af6358' : 'whitesmoke' }}\n        type=\"submit\"\n        value=\"Show All\"\n        onClick={updateFilter}\n      >\n        All <span> {totalNum || ''}</span>\n      </button>\n      <button\n        className=\"filter-button\"\n        id=\"filterBtn2\"\n        style={{ color: todoListFilterState === 'Show Uncompleted' ? '#af6358' : 'whitesmoke' }}\n        type=\"submit\"\n        value=\"Show Uncompleted\"\n        onClick={updateFilter}\n      >\n        Active <span>{totalUncompletedNum || ''}</span>\n      </button>\n      <button\n        className=\"filter-button\"\n        style={{ color: todoListFilterState === 'Show Completed' ? '#af6358' : 'whitesmoke' }}\n        type=\"submit\"\n        value=\"Show Completed\"\n        onClick={updateFilter}\n      >\n        Complete <span>{totalCompletedNum || ''}</span>\n      </button>\n      <button id={sortIconColor[todoListSortState]} type=\"submit\" onClick={toggleSort}>\n        <SortIcon />\n      </button>\n\n      <button id=\"unsortedGray\" type=\"submit\" onClick={toggleDisplayStats}>\n        {displayStats && totalNum ? (\n          <span id=\"statsSpan\">\n            <span id=\"highSpan\">{high || 0}</span>\n            <span id=\"mediumSpan\">{medium || 0}</span>\n            <span id=\"lowSpan\">{low || 0}</span>\n          </span>\n        ) : (\n          <EqualizerIcon />\n        )}\n      </button>\n      <button id=\"unsortedGray\" type=\"submit\" onClick={reset}>\n        <RefreshIcon />\n      </button>\n    </ul>\n  );\n};\n\nexport default TodoListFilters;\n"
  },
  {
    "path": "demo-zustand-todo/src/components/TodoQuickCheck.jsx",
    "content": "import React from 'react';\nimport Checkbox from '@mui/material/Checkbox';\nimport shallow from 'zustand/shallow';\nimport useToDoStore from '../store/store';\nimport { useEffect } from 'react';\n\nconst selector = (state) => ({\n  setAllComplete: state.setAllComplete,\n  checkBox: state.checkBox,\n  setCheckBox: state.setCheckBox,\n});\n\nconst TodoQuickCheck = () => {\n  const { setAllComplete, checkBox, setCheckBox } = useToDoStore(selector, shallow);\n\n  useEffect(() => setCheckBox());\n\n  return (\n    <div id=\"quickCheck\">\n      <Checkbox\n        disableRipple\n        checked={checkBox}\n        color=\"default\"\n        inputProps={{ 'aria-label': 'primary checkbox' }}\n        onClick={() => setAllComplete()}\n      />\n      All\n    </div>\n  );\n};\n\nexport default TodoQuickCheck;\n"
  },
  {
    "path": "demo-zustand-todo/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Palanquin:wght@300&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <title>Chromogen Zustand Demo To-Do</title>\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"./favicon.ico\" />\n  </head>\n  <body>\n    <div id=\"app\">\n      <script src=\"./bundle.js\"></script>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "demo-zustand-todo/src/index.js",
    "content": "/* eslint-disable react/jsx-filename-extension */\nimport React from 'react';\nimport App from './components/App';\nimport { createRoot } from 'react-dom/client';\n\nconst root = createRoot(document.getElementById('app'));\n\nroot.render(<App />);\n"
  },
  {
    "path": "demo-zustand-todo/src/store/store.js",
    "content": "import { chromogenZustandMiddleware } from 'chromogen';\nimport { create } from 'zustand';\n\nconst useToDoStore = create(\n  chromogenZustandMiddleware((set) => ({\n    todoListState: [],\n\n    todoListFilterState: 'Show All',\n\n    todoListSortState: false,\n\n    resetFiltersAndSorted: () =>\n      set(\n        () => ({ todoListFilterState: 'Show All', todoListSortState: false }),\n        false,\n        'resetFiltersAndSorted',\n      ),\n\n    toggleSort: () =>\n      set((state) => ({ todoListSortState: !state.todoListSortState }), false, 'toggleSort'),\n\n    setFilter: (filter) => set(() => ({ todoListFilterState: filter }), false, 'setFilter'),\n\n    quoteText: '',\n\n    changeQuoteText: (text) => set(() => ({ quoteText: text }), false, 'changeQuoteText', text),\n\n    quoteNumber: 0,\n\n    changeQuoteNumber: () =>\n      set(() => ({ quoteNumber: Math.floor(Math.random() * 1643) }), false, 'changeQuoteNumber'),\n\n    setAllComplete: () =>\n      set(\n        (state) => ({\n          todoListState: state.todoListState.some((todo) => todo.isComplete === false)\n            ? state.todoListState.map((todo) => {\n                return { ...todo, isComplete: true };\n              })\n            : state.todoListState.map((todo) => {\n                return { ...todo, isComplete: false };\n              }),\n        }),\n        false,\n        'setAllComplete',\n      ),\n\n    checkBox: false,\n\n    setCheckBox: () =>\n      set(\n        (state) => ({\n          checkBox: state.todoListState.some((todo) => todo.isComplete === false) ? false : true,\n        }),\n        false,\n        'setCheckBox',\n      ),\n\n    addTodoListItem: (todo) =>\n      set(\n        (state) => ({ todoListState: [...state.todoListState, todo] }),\n        false,\n        'addTodoListItem',\n        todo,\n      ),\n\n    deleteTodoListItem: (id) =>\n      set(\n        (state) => ({ todoListState: state.todoListState.filter((todo) => todo.id !== id) }),\n        false,\n        'deleteTodoListItem',\n        id,\n      ),\n\n    editItemText: (text, id) =>\n      set(\n        (state) => ({\n          todoListState: state.todoListState.map((todo) => {\n            if (todo.id === id) {\n              return { ...todo, text: text };\n            } else {\n              return todo;\n            }\n          }),\n        }),\n        false,\n        'editItemText',\n        text,\n        id,\n      ),\n\n    toggleItemCompletion: (id) =>\n      set(\n        (state) => ({\n          todoListState: state.todoListState.map((todo) => {\n            if (todo.id === id) {\n              return { ...todo, isComplete: !todo.isComplete };\n            } else {\n              return todo;\n            }\n          }),\n        }),\n        false,\n        'toggleItemCompletion',\n        id,\n      ),\n\n    searchResultState: {\n      all: {\n        searchTerm: '',\n        results: [],\n      },\n      high: {\n        searchTerm: '',\n        results: [],\n      },\n      medium: {\n        searchTerm: '',\n        results: [],\n      },\n      low: {\n        searchTerm: '',\n        results: [],\n      },\n    },\n\n    setSearchState: (searchTerm, priority) =>\n      set(\n        (state) => {\n          if (searchTerm === '')\n            return {\n              searchResultState: {\n                ...state.searchResultState,\n                [priority]: { searchTerm, results: [] },\n              },\n            };\n          let results = [...state.todoListState].filter((todo) => todo.text.includes(searchTerm));\n          if (priority !== 'all') results = results.filter((todo) => todo.priority === priority);\n          return {\n            searchResultState: { ...state.searchResultState, [priority]: { searchTerm, results } },\n          };\n        },\n        false,\n        'setSearchState',\n        searchTerm,\n        priority,\n      ),\n  })),\n);\n\nexport default useToDoStore;\n"
  },
  {
    "path": "demo-zustand-todo/src/styles/styles.css",
    "content": "*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n* {\n  margin: 0;\n}\nhtml,\nbody,\n#root, /* for create-react-app */\n#__next /* for Next.js */ {\n  height: 100%;\n}\nbody {\n  font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n}\nimg,\npicture,\nvideo,\ncanvas,\nsvg {\n  display: block;\n  max-width: 100%;\n}\ninput,\nbutton,\ntextarea,\nselect {\n  font: inherit;\n}\np,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  overflow-wrap: break-word;\n}\n#root,\n#__next {\n  isolation: isolate;\n}\n\n/* -------------General Styles---------------- */\nhtml {\n  margin: 0;\n  background-color: #2e3237;\n  color: #9ee6f7;\n  overflow-x: hidden;\n}\n\nh1 {\n  text-align: center;\n  font-size: 2.3rem;\n  color: #9ee6f7;\n  text-shadow: 1px 2px 2px rgba(250, 250, 250, 0.267);\n  letter-spacing: 3px;\n}\ninput,\nbutton,\nselect {\n  background-color: transparent;\n  /* border: 1px solid lightgray; */\n  border: none;\n  padding: 20px 0;\n  border-radius: 4px;\n  color: #9ee6f7;\n  font-size: 18px;\n  letter-spacing: 1px;\n}\n\n/* remove browser defaults */\nbutton:focus {\n  outline: none;\n}\ninput:focus {\n  outline: none;\n}\n/* ------------TodoList------------- */\n\n/* topmost container */\n.mainContainer {\n  display: flex;\n  flex-direction: column;\n  height: 100vh;\n  width: 100vw;\n  overflow-y: scroll;\n}\n\n.wrapper {\n  display: flex;\n  margin: 0 auto;\n  padding-inline: 32px;\n  width: 100%;\n  max-width: 800px;\n  height: 100%;\n  flex-direction: column;\n  padding-bottom: 24px;\n}\n\n/* overall row container for todo list display */\n.todosDisplayRow {\n  margin: 0 auto;\n  width: 100%;\n  height: 100%;\n  color: #9ee6f7;\n}\n\na {\n  color: #9ee6f7;\n  text-decoration: none;\n}\n\na:hover {\n  text-decoration: underline;\n}\n\n.todosDisplayRow h1 {\n  padding-block: 40px;\n}\n\n/* container for entire list display */\n.todosContainer {\n  background-color: rgb(255, 255, 255, 0.1);\n  box-shadow: 0px 0px 35px 20px rgba(10, 10, 10, 0.096);\n  border-radius: 5px;\n  padding: 5px 12px 0 12px;\n}\n\n/* -------------TodoItemCreator---------------- */\n\n.itemCreator {\n  display: flex;\n  border-bottom: 1px solid rgba(245, 245, 245, 0.336);\n}\n\n.itemCreator button {\n  padding: 0 24px;\n}\n\ninput::placeholder {\n  letter-spacing: 1.5px;\n}\n\n.itemCreator input {\n  padding: 0 0 0 12px;\n  flex-grow: 1;\n  height: 60px;\n}\n\n#radioContainer svg {\n  margin-top: 11px;\n  opacity: 0.7;\n}\n\nlabel.MuiFormControlLabel-root {\n  margin-left: 0;\n  margin-right: 0;\n}\n\n/* -------------TodoItem---------------- */\n\n.itemContainer,\n.lowPriority,\n.mediumPriority,\n.highPriority {\n  display: grid;\n  grid-template-columns: 79fr 14fr 7fr;\n  border-bottom: 1px solid rgba(245, 245, 245, 0.336);\n}\n\n/* dynamic checkbox color */\n.highPriority svg {\n  color: #f10101;\n  opacity: 0.7;\n  margin-right: 28px;\n}\n.mediumPriority svg {\n  color: #ffe600;\n  opacity: 0.7;\n  margin-right: 28px;\n}\n.lowPriority svg {\n  color: #05fb11;\n  opacity: 0.7;\n  margin-right: 28px;\n}\n\n#todoItem button {\n  margin-left: 7px;\n}\n\n/* -------------TodoListFilter---------------- */\n\nul {\n  display: grid;\n  grid-template-columns: 27fr 27fr 27fr 6fr 6fr 6fr;\n  margin: 0;\n  padding: 0px;\n  width: 100%;\n}\n\n.filter-button {\n  margin-top: 10px;\n  margin-bottom: 10px;\n  border-right: 1px solid rgba(234, 230, 230, 0.26);\n  padding: 10px 20px;\n  border-radius: 0px 0px 4px 4px;\n}\n\n/* dynamic sort icon color */\n#sortedWhite svg {\n  color: whitesmoke;\n}\n#unsortedGray svg {\n  color: rgba(245, 245, 245, 0.336);\n}\n#unsortedGray {\n  width: 64px;\n}\n\n#statsSpan {\n  display: grid;\n  grid-template-columns: 30fr 30fr 30fr;\n  align-items: center;\n}\n#highSpan {\n  color: #ef5350;\n  opacity: 0.7;\n  margin-right: 4px;\n}\n#mediumSpan {\n  color: #ffee58;\n  opacity: 0.7;\n  margin-right: 4px;\n}\n#lowSpan {\n  color: #66bb6a;\n  opacity: 0.7;\n}\n\n/* filter stats (number) */\nbutton span {\n  opacity: 0.6;\n}\n\n/* ----------QuoteBox---------- */\n\n#quoteContainer {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  /* justify-content: space-between; */\n  font-size: 18px;\n}\n\n#quoteContainer p {\n  white-space: pre-wrap;\n  padding-bottom: 24px;\n}\n\n.quoteBox {\n  display: flex;\n  flex-direction: row;\n  justify-content: space-between;\n  border: 1px solid rgba(234, 230, 230, 0.1);\n  /* background-color: rgb(63, 63, 63); */\n  /* box-shadow: 0px 0px 35px 20px rgba(10, 10, 10, 0.096); */\n  border-radius: 5px;\n  padding: 16px 24px;\n}\n\n.quoteBox img {\n  margin: 20px;\n  height: 150px;\n  width: 150px;\n}\n\n.quoteBox button {\n  width: 150px;\n  padding: 24 0;\n  text-align: left;\n}\n\n.quoteBox button:hover {\n  cursor: pointer;\n  /* background-color: rgba(255, 255, 255, 0.1); */\n}\n\n/* .quoteBox button:active {\n  background-color: #75acb990;\n} */\n\n/* ---------- TodoQuickCheck ---------- */\n\n/* #quickCheck {\n} */\n\n#quickCheck svg {\n  color: #9ee6f7;\n}\n\nbutton {\n  cursor: pointer;\n}\n\n/* ---------- Search ---------- */\n.searchContainer {\n  display: sticky;\n  margin-top: 100px;\n  background-color: rgb(255, 255, 255, 0.1);\n  box-shadow: 0px 0px 35px 20px rgba(10, 10, 10, 0.096);\n  border-radius: 5px;\n  padding: 5px 12px 12px 12px;\n  display: grid;\n  grid-template-columns: 70% 30%;\n}\n\n.searchField {\n  border-bottom: solid 1px whitesmoke;\n  border-radius: 0;\n  padding-left: 12px;\n}\n\n.prioritySelect {\n  grid-column-start: 2;\n  border-bottom: solid 1px whitesmoke;\n  border-radius: 0;\n  -webkit-appearance-select: none;\n}\n\n.searchResults {\n  grid-column-start: span 3;\n}\n\n#newChromogenLogo {\n  display: flex;\n  justify-content: center;\n  width: 400px;\n  height: 300px;\n}\n\nselect {\n  -webkit-appearance-select: none;\n  /* background: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='%238C98F2'><polygon points='0,0 100,0 50,50'/></svg>\")\n    no-repeat; */\n}\n\n/*  */\n\n.w-tc-editor[data-color-mode*='dark'],\n[data-color-mode*='dark'] .w-tc-editor,\n[data-color-mode*='dark'] .w-tc-editor-var,\nbody[data-color-mode*='dark'] {\n  --color-fg-default: #ddd;\n  --color-canvas-subtle: #161b22;\n  --color-prettylights-syntax-comment: #818c97;\n  --color-prettylights-syntax-entity-tag: #ed81b0;\n  --color-prettylights-syntax-entity: #d2a8ff;\n  --color-prettylights-syntax-sublimelinter-gutter-mark: #ddd;\n  --color-prettylights-syntax-constant: #ed8876;\n  --color-prettylights-syntax-string: #68afc8;\n  --color-prettylights-syntax-keyword: #ed81b0;\n  --color-prettylights-syntax-markup-bold: #c9d1d9;\n}\n"
  },
  {
    "path": "demo-zustand-todo/webpack.config.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  entry: path.resolve(__dirname, './src/index.js'),\n  output: {\n    filename: 'bundle.js',\n  },\n  devServer: {\n    contentBase: path.resolve(__dirname, './src'),\n    historyApiFallback: true,\n  },\n  mode: process.env.NODE_ENV,\n  module: {\n    rules: [\n      {\n        test: /\\.(js|jsx)$/,\n        exclude: /node_modules/,\n        loader: 'babel-loader',\n        options: {\n          presets: ['@babel/preset-env', '@babel/preset-react'],\n          plugins: ['@babel/transform-runtime'],\n        },\n      },\n      {\n        test: /\\.css$/,\n        use: [\n          {\n            loader: 'style-loader',\n          },\n          {\n            loader: 'css-loader',\n          },\n        ],\n      },\n      {\n        test: /\\.(png|jpg)$/,\n        use: ['file-loader', 'url-loader?limit=8192'],\n      },\n    ],\n  },\n  resolve: {\n    extensions: ['.js', '.jsx'],\n  },\n};\n"
  },
  {
    "path": "jenkins/Jenkinsfile",
    "content": "pipeline {\n    agent {\n        docker {\n            image 'node:lts-buster-slim'\n            args '-p 3003:3003'\n        }\n    }\n    environment {\n        CI = 'true'\n    }\n    stages {\n        stage('Build') {\n            steps {\n                sh 'npm --prefix ./package install'\n            }\n        }\n        stage('Test') {\n            steps {\n                sh './jenkins/scripts/test.sh'\n            }\n        }\n        stage('Deliver') {\n            steps {\n                sh './jenkins/scripts/deliver.sh'\n                input message: 'Finished using the web site? (Click \"Proceed\" to continue)'\n                sh './jenkins/scripts/kill.sh'\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "jenkins/scripts/deliver.sh",
    "content": "#!/usr/bin/env sh\n\necho 'The following \"npm\" command builds your Node.js/React application for'\necho 'production in the local \"build\" directory (i.e. within the'\necho '\"/var/jenkins_home/workspace/simple-node-js-react-app\" directory),'\necho 'correctly bundles React in production mode and optimizes the build for'\necho 'the best performance.'\nset -x\nnpm run build\nset +x\n\necho 'The following \"npm\" command runs your Node.js/React application in'\necho 'development mode and makes the application available for web browsing.'\necho 'The \"npm start\" command has a trailing ampersand so that the command runs'\necho 'as a background process (i.e. asynchronously). Otherwise, this command'\necho 'can pause running builds of CI/CD applications indefinitely. \"npm start\"'\necho 'is followed by another command that retrieves the process ID (PID) value'\necho 'of the previously run process (i.e. \"npm start\") and writes this value to'\necho 'the file \".pidfile\".'\nset -x\nnpm --prefix ./package run symlink &\nsleep 1\necho $! > .pidfile\nset +x\n\necho 'Now...'\necho 'Visit http://localhost:3003 to see your Node.js/React application in action.'\necho '(This is why you specified the \"args ''-p 3003:3003''\" parameter when you'\necho 'created your initial Pipeline as a Jenkinsfile..)'\n"
  },
  {
    "path": "jenkins/scripts/kill.sh",
    "content": "#!/usr/bin/env sh\n\necho 'The following command terminates the \"npm start\" process using its PID'\necho '(written to \".pidfile\"), all of which were conducted when \"deliver.sh\"'\necho 'was executed.'\nset -x\nkill $(cat .pidfile)\n"
  },
  {
    "path": "jenkins/scripts/test.sh",
    "content": "#!/usr/bin/env sh\n\necho 'The following \"npm\" command (if executed) installs the \"cross-env\"'\necho 'dependency into the local \"node_modules\" directory, which will ultimately'\necho 'be stored in the Jenkins home directory. As described in'\necho 'https://docs.npmjs.com/cli/install, the \"--save-dev\" flag causes the'\necho '\"cross-env\" dependency to be installed as \"devDependencies\". For the'\necho 'purposes of this tutorial, this flag is not important. However, when'\necho 'installing this dependency, it would typically be done so using this'\necho 'flag. For a comprehensive explanation about \"devDependencies\", see'\necho 'https://stackoverflow.com/questions/18875674/whats-the-difference-between-dependencies-devdependencies-and-peerdependencies.'\nset -x\nnpm install --save-dev cross-env\nset +x\n\necho 'The following \"npm\" command tests that your simple Node.js/React'\necho 'application renders satisfactorily. This command actually invokes the test'\necho 'runner Jest (https://facebook.github.io/jest/).'\nset -x\nnpm --prefix ./package run test\n"
  },
  {
    "path": "package/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 OSLabs Beta\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "package/README.md",
    "content": "<div align=\"center\">\n<h1>Chromogen</h1>\n<a href=\"https://github.com/open-source-labs/Chromogen\">\n  <img\n    height=\"120\"\n    width=\"120\"\n    alt=\"chromogen logo\"\n    src=\"https://github.com/open-source-labs/Chromogen/raw/master/assets/logo/Chromogen.png\"\n  />\n</a>\n\n<h3>A UI-driven Jest test-generation package for <a href=\"https://www.npmjs.com/package/recoil\">Recoil.js</a> selectors and <a href=\"https://www.npmjs.com/package/zustand\">Zustand</a> store hooks.</h3>\n\n<br />\n\n[![npm version](https://img.shields.io/npm/v/chromogen)](https://www.npmjs.com/package/chromogen)\n[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/open-source-labs/Chromogen/blob/master/LICENSE)\n<br />\n\n</div>\n<br />\n\n**Now Compatible with React V18**\n\nChromogen (Now on Version 4.0) is a Jest unit-test generation tool for Zustand Stores and Recoil selectors. It captures state changes during user interaction and auto-generates corresponding test suites. Simply launch your application after following the installation instructions below, interact as a user normally would, and with one click you can download a ready-to-run Jest test file. Alternatively, you can copy the generated tests straight to your clipboard.\n<br /><br /><br />\n\n## Installation for Zustand Apps\n\nBefore using Chromogen, you'll need to make two changes to your application:\n\n1. Import the `<ChromogenZustandObserver />` component and render it alongside any other components in `<App />`\n2. Import `chromogenZustandMiddleware` function from Chromogen. This will be used as middleware when setting up your store.\n\n### Import the ChromogenZustandObserver component\n\nImport `ChromogenZustandObserver`. ChromogenZustandObserver can be rendered alongside any other components in `<App />`.\n\n```jsx\nimport React from 'react';\nimport { ChromogenZustandObserver } from 'chromogen';\nimport TodoList from './TodoList';\n\nconst App = () => (\n  <>\n    <ChromogenZustandObserver />\n    <TodoList />\n  </>\n);\n\nexport default App;\n```\n\nImport `chromogenZustandMiddleware`. When you call create, wrap your store function with chromogenZustandMiddleware. **Note**, when using chromogenZustandMiddleware, you'll need to provide some additional arguments into the set function.\n\n1. _Overwrite State_ (boolean) - Without middleware, this defaults to `false`, but you'll need to explicitly provide a value when using Chromogen.\n2. _Action Name_ - Used for test generation\n3. _Action Parameters_ - If the action requires input parameters, pass these in after the Action Name.\n\n```jsx\nimport { chromogenZustandMiddleware } from 'chromogen';\nimport create from 'zustand';\n\nconst useStore = create(\n  chromogenZustandMiddleware((set) => ({\n    counter: 0,\n    color: 'black',\n    prioritizeTask: ['walking', 5],\n    addCounter: () => set(() => ({ counter: (counter += 1) }), false, 'addCounter'),\n    changeColor: (newColor) => set(() => ({ color: newColor }), false, 'changeColor', newColor),\n    setTaskPriority: (task, priority) =>\n      set(() => ({ prioritizeTask: [task, priority] }), false, 'setTaskPriority', task, priority),\n  })),\n);\n\nexport default useStore;\n```\n\n<br><hr>\n\n## Installation for Recoil Apps\n\nBefore running Chromogen, you'll need to make two changes to your application:\n\n1. Import the `<ChromogenObserver />` component as a child of `<RecoilRoot />`\n1. Import the `atom` and `selector` functions from Chromogen instead of Recoil\n\n<i>Note: These changes do have a small performance cost, so they should be reverted before deploying to production.</i>\n\n<br>\n\n### Import the ChromogenObserver component\n\nChromogenObserver should be included as a direct child of RecoilRoot. It does not need to wrap any other components, and it takes no mandatory props. It utilizes Recoil's TransactionObserver Hook to record snapshots on state change.\n\n```jsx\nimport React from 'react';\nimport { RecoilRoot } from 'recoil';\nimport { ChromogenObserver } from 'chromogen';\nimport MyComponent from './components/MyComponent.jsx';\n\nconst App = (props) => (\n  <RecoilRoot>\n    <ChromogenObserver />\n    <MyComponent {...props} />\n  </RecoilRoot>\n);\n\nexport default App;\n```\n\nIf you are using pseudo-random key names, such as with _UUID_, you'll need to pass all of your store exports to the ChromogenObserver component as a `store` prop. This will allow Chromogen to use source code variable names in the output file, instead of relying on keys. When all atoms and selectors are exported from a single file, you can pass the imported module directly:\n\n```jsx\nimport * as store from './store';\n// ...\n<ChromogenObserver store={store} />;\n```\n\nIf your store utilizes seprate files for various pieces of state, you can pass all of the imports in an array:\n\n```jsx\nimport * as atoms from './store/atoms';\nimport * as selectors from './store/selectors';\nimport * as misc from './store/arbitraryRecoilState';\n// ...\n<ChromogenObserver store={[atoms, selectors, misc]} />;\n```\n\n<br>\n\n### Import atom & selector functions from Chromogen\n\nWherever you import `atom` and/or `selector` functions from Recoil (typically in your `store` file), import them from Chromogen instead. The arguments passed in do **not** need to change in any away, and the return value will still be a normal RecoilAtom or RecoilSelector. Chromogen wraps the native Recoil functions to track which pieces of state have been created, as well as when various selectors are called and what values they return.\n\n```js\nimport { atom, selector } from 'chromogen';\n\nexport const fooState = atom({\n  key: 'fooState',\n  default: {},\n});\n\nexport const barState = selector({\n  key: 'barState',\n  get: ({ get }) => {\n    const derivedState = get(fooState);\n    return derivedState.baz || 'value does not exist';\n  },\n});\n```\n\n<br><hr>\n\n## Usage for All Apps\n\nAfter following the installation steps above, launch your application as normal. You should see two buttons in the bottom left corner.\n\n<div align=\"center\">\n\n![Buttons](https://github.com/open-source-labs/Chromogen/raw/master/assets/README-root/ultratrimmedDemo.gif)\n\n</div>\n\nThe pause button on the left is the **pause recording** button. Clicking it will pause recording, so that no tests are generated during subsequent state changes. Pausing is useful for setting up a complex initial state with repetitive actions, where you don't want to test every step of the process.\n\nThe button in the middle is the **download** button. Clicking it will download a new test file that includes _all_ tests generated since the app was last launched or refreshed.\n\nThe button on the right is the **copy-to-clipboard** button. Clicking it will copy your tests, including _all_ tests generated since the app was last launched or refreshed.\n\nOnce you've recorded all the interactions you want to test, click the pause button and then the download button to generate the test file or press copy to copy to your clipboard. You can now drag-and-drop the downloaded file into your app's test directory or paste the code in your new file. **Don't forget to add the source path in your test file**\n\nYou're now ready to run your tests! After running your normal Jest test command, you should see a test suite for `chromogen.test.js`.\n\nThe current tests check whether state has changed after an interaction and checks whether the resulting state change variables have been updated as expected.\n\n<br><hr>\n\nPlease visit our [main repo](https://github.com/open-source-labs/Chromogen) for more detailed instructions, as well as any bug reports, support issues, or feature requests.\n"
  },
  {
    "path": "package/babel.config.js",
    "content": "module.exports = {\n  presets: [\n    ['@babel/preset-env', { targets: { node: 'current' } }],\n    '@babel/preset-react',\n    '@babel/preset-typescript',\n  ],\n};\n"
  },
  {
    "path": "package/index.ts",
    "content": "/* eslint-disable */\nimport { atom, selector, atomFamily, selectorFamily } from './recoil_generator/src/api/api';\nimport { ChromogenZustandObserver } from './zustand_generator/src/component/ChromogenZustandObserver';\nimport { ChromogenObserver } from './recoil_generator/src/component/ChromogenObserver';\nimport { chromogenZustandMiddleware } from './zustand_generator/src/api/api';\nimport Editor from './zustand_generator/src/component/Editor';\n\n// CHROMGOEN FAMILY APIs ARE CURRENTLY UNSTABLE\nexport {\n  atom,\n  selector,\n  atomFamily,\n  selectorFamily,\n  ChromogenObserver,\n  chromogenZustandMiddleware,\n  ChromogenZustandObserver,\n  Editor,\n};\n"
  },
  {
    "path": "package/package.json",
    "content": "{\n  \"name\": \"chromogen\",\n  \"version\": \"5.0.1\",\n  \"description\": \"simple, interaction-driven Jest test generator for Recoil and React Hooks apps\",\n  \"main\": \"build/index.js\",\n  \"keywords\": [\n    \"react\",\n    \"recoil\",\n    \"jest\",\n    \"testing\"\n  ],\n  \"files\": [\n    \"build\"\n  ],\n  \"scripts\": {\n    \"prepublishOnly\": \"npm run build\",\n    \"build\": \"tsc\",\n    \"test\": \"jest --verbose --coverage\",\n    \"localUpdate\": \"tsc && npm --prefix ../demo-zustand-todo run update\",\n    \"tarballUpdate\": \"npm --prefix ../demo-zustand-todo run symlink\",\n    \"coveralls\": \"cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/open-source-labs/Chromogen.git\"\n  },\n  \"contributors\": [\n    {\n      \"name\": \"Brach Burdick\",\n      \"url\": \"https://github.com/sirbrachthepale/\"\n    },\n    {\n      \"name\": \"Francois Denavaut\",\n      \"url\": \"https://github.com/dnvt/\"\n    },\n    {\n      \"name\": \"Maggie Kwan\",\n      \"url\": \"https://github.com/maggiekwan/\"\n    },\n    {\n      \"name\": \"Lawrence Liang\",\n      \"url\": \"https://github.com/Lawliang/\"\n    },\n    {\n      \"name\": \"Michelle Holland\",\n      \"url\": \"https://github.com/michellebholland/\"\n    },\n    {\n      \"name\": \"Jim Chen\",\n      \"url\": \"https://github.com/chenchingk\"\n    },\n    {\n      \"name\": \"Andy Wang\",\n      \"url\": \"https://github.com/andywang23\"\n    },\n    {\n      \"name\": \"Connor Rose Delisle\",\n      \"url\": \"https://github.com/connorrose\"\n    },\n    {\n      \"name\": \"Amy Yee\",\n      \"url\": \"https://github.com/amyy98\"\n    },\n    {\n      \"name\": \"Cameron Greer\",\n      \"url\": \"https://github.com/cgreer011\"\n    },\n    {\n      \"name\": \"Jinseon Shin\",\n      \"url\": \"https://github.com/wlstjs\"\n    },\n    {\n      \"name\": \"Nicholas Shay\",\n      \"url\": \"https://github.com/nicholasjs\"\n    },\n    {\n      \"name\": \"Ryan Tumel\",\n      \"url\": \"https://github.com/rtumel123\"\n    },\n    {\n      \"name\": \"Marcellies Pettiford\",\n      \"url\": \"https://github.com/mp-04\"\n    },\n    {\n      \"name\": \"Sung Kim\",\n      \"url\": \"https://github.com/smk53664\"\n    },\n    {\n      \"name\": \"Lina Lee\",\n      \"url\": \"https://github.com/lina4lee\"\n    },\n    {\n      \"name\": \"Erica Oh\",\n      \"url\": \"https://github.com/ericaysoh\"\n    },\n    {\n      \"name\": \"Dani Almaraz\",\n      \"url\": \"https://github.com/dtalmaraz\"\n    },\n    {\n      \"name\": \"Craig Boswell\",\n      \"url\": \"https://github.com/crgb0s\"\n    },\n    {\n      \"name\": \"Hussein Ahmed\",\n      \"url\": \"https://github.com/Hali3030\"\n    },\n    {\n      \"name\": \"Ian Kila\",\n      \"url\": \"https://github.com/iannkila\"\n    },\n    {\n      \"name\": \"Yuehao Wong\",\n      \"url\": \"https://github.com/yuehaowong\"\n    }\n  ],\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/open-source-labs/Chromogen/issues\"\n  },\n  \"homepage\": \"https://github.com/open-source-labs/Chromogen#readme\",\n  \"peerDependencies\": {\n    \"jest\": \">=24.0.0\",\n    \"typescript\": \">=3.8.0\"\n  },\n  \"dependencies\": {\n    \"@uiw/react-textarea-code-editor\": \"^2.1.1\",\n    \"dependency-cruiser\": \"^12.9.0\",\n    \"react\": \"^18.0.0\",\n    \"react-dom\": \"^18.0.0\",\n    \"recoil\": \"^0.7.2\",\n    \"redux\": \"^4.0.5\",\n    \"styled-components\": \"^5.3.6\",\n    \"zustand\": \"^4.1.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.11.6\",\n    \"@babel/preset-env\": \"^7.11.5\",\n    \"@babel/preset-react\": \"^7.10.4\",\n    \"@babel/preset-typescript\": \"^7.10.4\",\n    \"@testing-library/react\": \"^13.1.1\",\n    \"@types/node\": \"^14.11.2\",\n    \"@types/react\": \"^18.0.6\",\n    \"@types/react-dom\": \"^18.0.2\",\n    \"@types/styled-components\": \"^5.1.26\",\n    \"babel-jest\": \"^26.3.0\",\n    \"coveralls\": \"^3.1.0\",\n    \"css-loader\": \"^6.7.3\",\n    \"eslint-config-airbnb-typescript\": \"^17.0.0\",\n    \"jest\": \"^26.6.3\",\n    \"react-test-renderer\": \"^18.0.0\",\n    \"typescript\": \"^4.0.3\"\n  }\n}\n"
  },
  {
    "path": "package/recoil_generator/__tests__/api.test.js",
    "content": "import { ledger } from '../src/utils/ledger.ts';\nimport { atom, selector, selectorFamily, atomFamily } from '../src/api/api.ts';\n\n// testing the atom\ndescribe('atom', () => {\n  // destructuring atoms from ledger interface in utils folder\n  const { atoms } = ledger;\n  it('is a function', () => {\n    expect(typeof atom).toBe('function');\n  });\n\n  it('should update ledger upon invocation', () => {\n    // creating a mock atom\n    atom({\n      key: 'exampleAtom',\n      default: false,\n    });\n    // verifying atoms property (array) on ledger has been updated with input atom\n    expect(atoms).toHaveLength(1);\n  });\n\n  it('should create Recoil atom with correct key name', () => {\n    // verifying that input atom key matches 'exampleAtom'\n    expect(atoms[0]).toHaveProperty('key', 'exampleAtom');\n  });\n});\n\ndescribe('selector', () => {\n  // destructuring selectors from ledger object in utils folder\n  const { selectors } = ledger;\n  const test = true;\n\n  it('is a function', () => {\n    // verify selector is a function\n    expect(typeof selector).toBe('function');\n  });\n\n  it('should update ledger upon invocation', () => {\n    // creating a mock selector with key, get, set\n    selector({\n      key: 'exampleSelector',\n      get: () => 'getMethod',\n      set: () => 'setMethod',\n    });\n    // verify selectors property in ledger has been updated with mock selector\n    expect(selectors).toHaveLength(1);\n  });\n  // verifying that input selector key matches 'exampleSelector'\n  it('should capture correct key name', () => {\n    expect(selectors[0]).toEqual('exampleSelector');\n  });\n\n  xit('should return an object if an input condition evaluates to true', () => {\n    // verify that selector (recoilSelector in this context) invocation returns an object\n    expect(typeof selector(test)).toBe('object');\n  });\n});\n\ndescribe('atomFamily', () => {\n  it('should return a function', () => {\n    // create a mock atomFamily\n    const familyFactory = atomFamily({\n      key: 'familyKey',\n      default: (param) => param.toString(),\n    });\n    // verify that familyFactory is a function\n    expect(typeof familyFactory).toEqual('function');\n  });\n});\n\ndescribe('selectorFamily', () => {\n  // truthy parameter\n  const test = true;\n  it('should return a function', () => {\n    // create a mock selectorFamily\n    const familyFactory = selectorFamily({\n      key: 'familyKey',\n      get: () => () => 'some value',\n      set: () => () => undefined,\n      default: (param) => param.toString(),\n    });\n    // verify that familyFactory is a function\n    expect(typeof familyFactory).toEqual('function');\n  });\n  it('should return an object if an input condition evaluates to true', () => {\n    // verify that selectorFamily (recoilSelectorFamily in this context) invocation returns an object\n    expect(typeof selectorFamily(test)).toBe('function');\n  });\n});\n"
  },
  {
    "path": "package/recoil_generator/__tests__/component-utils.test.js",
    "content": "import { ledger } from '../src/utils/ledger.ts';\nimport {generateFile} from '../src/component/component-utils';\n\n// Testing generateFile\nxdescribe('generateFile', () => {\n  const setFile = 0;\n  const array = [[], [], []];\n  let storeMap = new Map(array);\n\n  const {\n    atoms,\n    selectors,\n    setters,\n    atomFamilies,\n    selectorFamilies,\n    initialRender,\n    initialRenderFamilies,\n    transactions,\n    setTransactions,\n  } = ledger;\n\n  // We expect our generate file to not be the falsy return statement, which is the entirety of the ledger, with atoms being the new user input\n  generateFile(setFile, storeMap);\n});\n"
  },
  {
    "path": "package/recoil_generator/__tests__/component.test.js",
    "content": "import React from 'react';\nimport { RecoilRoot, useRecoilState } from 'recoil';\nimport { render } from '@testing-library/react';\nimport { ChromogenObserver } from '../src/component/ChromogenObserver.tsx';\nimport { ledger } from '../src/utils/ledger.ts';\nimport { atom } from '../src/api/api.ts';\n\n// import {shallow} from 'enzyme';\n// import {mount} from 'enzyme';\n\ndescribe('chromogenObserver', () => {\n  global.URL = {\n    createObjectURL: () => 'http://mockURL.com',\n  };\n\n  beforeEach(() => {\n    console.error = jest.fn();\n    // creating a mockAtom\n    const mockAtom = atom({ key: 'mockAtom', default: true });\n    // create a functional mockComponent \n    const MockComponent = () => {\n      // declaring a React Hook using mockAtom as recoilState\n      const [mock, setMock] = useRecoilState(mockAtom);\n      // render a mock-button that toggles mock recoilState onclick\n      return <button id=\"mock-button\" type=\"button\" onClick={() => setMock(!mock)} />;\n    };\n\n    render(\n      <RecoilRoot>\n        <ChromogenObserver />\n        <MockComponent />\n      </RecoilRoot>,\n    );\n  });\n\n  // ChromogenObserver lines 25-36\n  it('should relay messages to DevTool', () => {\n\n    // expect(window.addEventListener('message', 'connectChromogen')).toBeTruthy()\n\n    // expect(window.addEventListener('message', 'downloadFile')).toBeTruthy()\n\n    // expect(window.addEventListener('message', 'toggleRecord')).toBeTruthy()\n  });\n\n  // ChromogenObserver lines 56-80\n  // Store contains atoms and selectors\n  it('should update storeMap with all items from passed in store', () => {\n    \n  })\n\n  // ChromogenObserver lines 104-117\n  it('should update atomFamilyState', () => {\n\n  })\n\n  // ChromogenObserver lines 142-145\n  it('should ', () => {\n\n  })\n\n  // ChromogenObserver lines 154-155\n  it('should change button color on mouse enter/leave', () => {\n    \n  })\n\n  it('should render a download link', () => {\n    // verify that download Chromogen tests link exists and is being rendered\n    expect(document.getElementById('chromogen-download')).toBeTruthy();\n  });\n\n  it('should render two buttons by default', () => {\n    // verify that generate-file and record buttons are being rendered\n    expect(document.getElementById('chromogen-generate-file')).toBeTruthy();\n    expect(document.getElementById('chromogen-toggle-record')).toBeTruthy();\n  });\n\n  xit('should create a file URL on button click', () => {\n    // invoking a click on generate-file button\n    document.getElementById('chromogen-generate-file').click();\n    // declaring a const downloadLink referencing hidden download link\n    const downloadLink = document.getElementById('chromogen-download');\n    // verify that download link has a URL that is being referenced\n    expect(downloadLink.getAttribute('href')).toBeTruthy();\n  });\n\n  it('should create transactions when state updates', () => {\n    // invoking a click to check that state in mock-button is being toggled (in the state mock)\n    document.getElementById('mock-button').click();\n\n    // Using Promise to get around async nature of Recoil transactions\n    expect(\n      // verifying that resolve (mockData) has been added to ledger.transactions\n      new Promise((resolve) => setTimeout(() => resolve(ledger.transactions), 100)),\n      // verify that resolve property on promise object has been updated\n    ).resolves.toHaveLength(1);\n  });\n});\n"
  },
  {
    "path": "package/recoil_generator/__tests__/core-utils.test.jx",
    "content": "import {\n  debouncedAddToTransactions,\n  wrapGetter,\n  wrapSetter,\n} from '../src/api/core-utils';\n\nimport { debounce } from '../src/utils/utils';\n\nxdescribe('debouncedAddToTransaction', () => {\n\n});\n\nxdescribe('wrapGetter', () => {\n\n});\n\nxdescribe('wrapSetter', () => {\n\n});"
  },
  {
    "path": "package/recoil_generator/__tests__/output-utils.test.js",
    "content": "import {\n  initializeAtoms,\n  assertState,\n  testSelectors,\n  testSetters,\n  importRecoilFamily,\n  atomFamilyHook,\n  //writeableHook,\n  readableHook,\n} from '../src/output/output-utils.ts';\n\n// testing ternary operator in initializeAtoms helper function\ndescribe('initializeAtoms', () => {\n  // create mock atomUpdate object, follows AtomUpdate interface\n  const atomUpdate = {\n    key: 'testAtom',\n    value: 2,\n    previous: 1,\n    updated: true,\n  };\n\n  it('should set correct atom value if current is true', () => {\n    // create variable to hold evaluated result of invoking initializeAtoms on the mock array with a parameter of true\n    const returnString = initializeAtoms([atomUpdate], true);\n    // verify that returnString contains key property of a string and contains a value property on the atomUpdate object (since true was passed into 'initializeAtoms')\n    expect(returnString).toEqual(\n      expect.stringContaining(`result.current.set${atomUpdate.key}(${atomUpdate.value})`),\n    );\n  });\n\n  it('should set correct atom value if current is false', () => {\n    // create variable to hold evaluated result of invoking initializeAtoms on the mock array with a parameter of false\n    const returnString = initializeAtoms([atomUpdate], false);\n    // verify that returnString contains key property of a string and contains a value property on the atomUpdate object (since false was passed into 'initializeAtoms')\n    expect(returnString).toEqual(\n      expect.stringContaining(`result.current.set${atomUpdate.key}(${atomUpdate.previous})`),\n    );\n  });\n});\n\ndescribe('assertState', () => {\n  // create mock selectors array, follows SelectorUpdate interface\n  const selectorUpdates = [\n    {\n      key: 'testSelector1',\n      value: true,\n    },\n    {\n      key: 'testSelector2',\n      value: 100,\n    },\n  ];\n\n  it('should assert on each selector value', () => {\n    // create variable to hold evaluated result of invoking assertState on mock array\n    const returnString = assertState(selectorUpdates);\n    // verify that output test contains a string checking that the object's key equals a stringified version of its value on the same object\n    expect(returnString).toEqual(\n      expect.stringContaining(\n        `expect(result.current.${selectorUpdates[0].key}Value).toStrictEqual(${JSON.stringify(\n          selectorUpdates[0].value,\n        )});`,\n      ),\n    );\n\n    expect(returnString).toEqual(\n      expect.stringContaining(\n        `expect(result.current.${selectorUpdates[1].key}Value).toStrictEqual(${JSON.stringify(\n          selectorUpdates[1].value,\n        )});`,\n      ),\n    );\n  });\n});\n\ndescribe('importRecoilFamily', () => {\n  const familyObj = {\n    familyName: 'string',\n    atomName: 'test',\n  };\n  it('should return a string with an object as its parameter', () => {\n    expect(typeof importRecoilFamily(familyObj)).toBe('string');\n  });\n});\n\ndescribe('readableHook', () => {\n  const keyArray = ['one', 'two', 'chromogen'];\n  it('should return a string', () => {\n    expect(typeof readableHook(keyArray)).toBe('string');\n  });\n});\n\n// describe('writeableHook', () => {\n//   const keyArray = ['chromo', 'gen', 'chromogen'];\n//   it('should return a string', () => {\n//     expect(typeof writeableHook(keyArray)).toBe('string');\n//   });\n// });\n\ndescribe('testSelectors', () => {\n  it('should scrub special characters from key names', () => {\n    // create instance of invoking testSelectors on mock array that follows the Transaction interface\n    const returnString = testSelectors([\n      {\n        state: [\n          {\n            key: 'atom1',\n            value: 1,\n            previous: 2,\n            updated: true,\n          },\n        ],\n        updates: [\n          {\n            key: 'selector1',\n            value: 3,\n          },\n        ],\n        atomFamilyState: [\n          {\n            family: 'familyName1',\n            key: 'spec!alCh@r',\n            value: 4,\n            updated: true,\n          },\n        ],\n        familyUpdates: [\n          {\n            key: 'familyUpdate1',\n            value: 5,\n            params: 'params',\n          },\n        ],\n      },\n    ]);\n    // verify that if key property's value is a string with special characters they will be removed\n    expect(returnString).toEqual(expect.not.stringContaining('spec!alCh@r'));\n  });\n});\n\n// covers branch test percentage in testSetters\ndescribe('testSetters', () => {\n  // create mock array with setter object\n  const setTransactionsArrayWithSetter = [\n    {\n      state: [\n        {\n          key: 'atom1',\n          value: 1,\n          previous: 0,\n          updated: true,\n        },\n      ],\n      setter: {\n        key: 'selector1',\n        value: 2,\n        params: 'spec!alCh@r',\n      },\n    },\n  ];\n  // create mock array without setter object\n  const setTransactionsArrayWithoutSetter = [\n    {\n      state: [\n        {\n          key: 'atom1',\n          value: 1,\n          previous: 0,\n          updated: true,\n        },\n      ],\n    },\n  ];\n  const truthyReturnString = testSetters(setTransactionsArrayWithSetter);\n  const falsyReturnString = testSetters(setTransactionsArrayWithoutSetter);\n\n  it('should scrub special characters from params', () => {\n    // verify that if params property's value is a string with special characters, they will be removed\n    expect(truthyReturnString).toEqual(expect.not.stringContaining('spec!alCh@r'));\n  });\n  it('should return a string if an array is passed in', () => {\n    // verify that a string is returned if provided an array with out a setter object\n    expect(typeof falsyReturnString).toBe('string');\n  });\n});\n\n// TEST FOR ATOMFAMILYHOOK lines 70-81\n//create mock transactionArray\nxdescribe('atomFamilyHook', () => {\n  const transactionArray = [\n    {\n      atomFamilyState: [\n        {\n          key: 'spec!alCh@rspec!alCh@r',\n          family: 'familyName',\n          value: 10,\n          updated: true,\n        },\n      ],\n      familyUpdates: [\n        {\n          key: 'familyUpdate1',\n          value: 5,\n          params: 'params',\n        },\n      ],\n    },\n  ]; // truthy\n\n  const transactionArray2 = []; // falsy\n\n  const truthyReturnStr = atomFamilyHook(transactionArray);\n  const falsyReturnStr = atomFamilyHook(transactionArray2);\n\n  it('should scrub special characters', () => {\n    expect(truthyReturnStr).toEqual(expect.not.stringContaining('spec!alCh@r'));\n  });\n\n  it('should return empty string when transactionsArr length is falsy', () => {\n    expect(falsyReturnStr).toBe('');\n  });\n});\n"
  },
  {
    "path": "package/recoil_generator/__tests__/output.test.js",
    "content": "import { setFilter, output } from '../src/output/output.ts';\n\n// testing setFilter function\ndescribe('setFilter', () => {\n  it('should remove setter keys from array of selector keys', () => {\n    // create mock selectors array\n    const selectors = ['one', 'two', 'three'];\n    // create mock setters array\n    const setters = ['one'];\n    // store evaluated result of invoking setFilter on the mock data in an array\n    const filtered = setFilter(selectors, setters);\n    // verify that the info from setters caused a matching value to be removed from the selectors array ('one')\n    expect(filtered).not.toContain('one');\n  });\n});\n\ndescribe('output', () => {\n  it('should return a string', () => {\n    // create mock ledger object \n    const mockLedger = {\n      atoms: [],\n      selectors: [],\n      setters: [],\n      atomFamilies: [],\n      selectorFamilies: [],\n      initialRender: [],\n      initialRenderFamilies: [],\n      transactions: [],\n      setTransactions: [],\n    };\n    // verify that type of mockLedger is a string after output function is invoked on it\n    expect(typeof output(mockLedger)).toEqual('string');\n  });\n});\n"
  },
  {
    "path": "package/recoil_generator/__tests__/utils.test.js",
    "content": "import { debounce, convertFamilyTrackerKeys } from '../src/utils/utils.ts';\n\njest.useFakeTimers();\n\ndescribe('debounce', () => {\n  it('should return a new function', () => {\n    // declare mock function that returns a string\n    const inputFunction = () => 'example';\n    // declare a function that stores the evaluated result of invoking debounce on inputFunction with a wait time of 0s\n    const outputFunction = debounce(inputFunction, 0);\n    // verify that outputFunction is a function\n    expect(typeof outputFunction).toBe('function');\n    // verify that that the 'debounced function' (outputFunction) is different than the parameter function (inputFunction)\n    expect(outputFunction).not.toBe(inputFunction);\n  });\n\n  it('should limit consecutive calls', () => {\n    // increment count to 1 after 100ms\n    let count = 0;\n    const increment = debounce(() => {\n      count += 1;\n    }, 100);\n    // invoke increment twice\n    increment();\n    increment();\n    // advance timer to 101ms using a mock jest function\n    jest.advanceTimersByTime(101);\n    // verify that count only incremented once because it was debounced\n    expect(count).toEqual(1);\n  });\n});\n\n// testing convertFamilyTrackerKeys\ndescribe('convertFamilyTrackerKeys', () => {\n  it('should update key names if in map', () => {\n    // create mock tracker (object)\n    const newTracker = convertFamilyTrackerKeys(\n      // first parameter is an object with a property whose value is a string\n      { keyOne: 'some value' },\n      // second parameter is a new Map \n      new Map([['keyOne', 'keyUpdated']]),\n    );\n    // verify that newTracker object includes property from Map (keyUpdated)\n    expect(newTracker).toHaveProperty('keyUpdated');\n    // verify that newTracker did not update key name with first parameter\n    expect(newTracker).not.toHaveProperty('keyOne');\n  });\n\n  it('should preserve key names if not in map', () => {\n    // create mock tracker (object)\n    const newTracker = convertFamilyTrackerKeys(\n      { keyOne: 'some value' },\n      new Map([['keyTwo', 'keyNotUpdated']]),\n    );\n    // verify that newTracker's first parameter has been preserved\n    expect(newTracker).toHaveProperty('keyOne');\n    // verify that newTracker does have keyTwo in first parameter\n    expect(newTracker).not.toHaveProperty('keyTwo');\n  });\n});\n"
  },
  {
    "path": "package/recoil_generator/src/api/api.ts",
    "content": "/* eslint-disable */\nimport type {\n  RecoilState,\n  RecoilValueReadOnly,\n  AtomOptions,\n  ReadWriteSelectorOptions,\n  ReadOnlySelectorOptions,\n  SerializableParam,\n  AtomFamilyOptions,\n  ReadWriteSelectorFamilyOptions,\n  ReadOnlySelectorFamilyOptions,\n} from 'recoil';\nimport type { SelectorConfig, SelectorFamilyConfig } from '../types';\n\nimport {\n  selector as recoilSelector,\n  atom as recoilAtom,\n  atomFamily as recoilAtomFamily,\n  selectorFamily as recoilSelectorFamily,\n} from 'recoil';\nimport { wrapGetter, wrapSetter } from './core-utils';\nimport { dummyParam } from '../utils/utils';\nimport { ledger } from '../utils/ledger';\nimport { wrapFamilyGetter, wrapFamilySetter } from './family-utils';\n/* eslint-enable */\n\n/**\n * If transactions.length is greater than 1, the selector is being created after the initial render\n * (i.e. a dynamically generated selector) and will not be tracked. Doing so would break the imports\n * and assertions within the output test file. Same logic is applied to new atoms.\n *\n * If get is undefined, native Async, or Babel-transpiled generator-based async (id'd via RegEx),\n * we don't do any injecting or tracking. Selector just gets created & returned back out.\n *\n * Otherwise, we attempt to wrap get & set methods with custom functions that log the return\n * value on each transaction to the corresponding ledger array.\n *\n * If get returns a promise on page load, we delete selector from the selectors array\n * and do not track it on subsequent calls (using \"returnedPromise\" flag, since we can't \"un-inject\").\n */\n\n/* ----- SELECTOR ----- */\nexport function selector<T>(options: ReadWriteSelectorOptions<T>): RecoilState<T>;\nexport function selector<T>(options: ReadOnlySelectorOptions<T>): RecoilValueReadOnly<T>;\n// Overload function signature\nexport function selector(config: ReadWriteSelectorOptions<any> | ReadOnlySelectorOptions<any>) {\n  const { key, get } = config;\n  const { transactions, selectors, setters } = ledger;\n  if (\n    transactions.length > 0\n    || !get\n    || get.constructor.name === 'AsyncFunction'\n    || get.toString().match(/^\\s*return\\s*_.*\\.apply\\(this, arguments\\);$/m)\n  ) {\n    return recoilSelector(config);\n  }\n\n  // Wrap get method with tracking logic & update config\n  const getter = wrapGetter(key, get);\n  const newConfig: SelectorConfig<any> = { key, get: getter };\n\n  // Add setter to newConfig only if set method is defined\n  if ('set' in config) {\n    const setter = wrapSetter(key, config.set);\n    newConfig.set = setter;\n    setters.push(key);\n  }\n\n  // Create selector & add to ledger\n  const trackedSelector = recoilSelector(newConfig);\n  selectors.push(trackedSelector.key);\n  return trackedSelector;\n}\n\n/* ----- ATOM ----- */\nexport function atom<T>(config: AtomOptions<T>): RecoilState<T> {\n  const { transactions, atoms } = ledger;\n  const newAtom = recoilAtom(config);\n\n  // Can't use key-only b/c atoms must be passed to getLoadable during transaction iteration\n  if (transactions.length === 0) atoms.push(newAtom);\n\n  return newAtom;\n}\n\n/* ----- ATOM FAMILY ----- */\nexport function atomFamily<T, P extends SerializableParam>(\n  config: AtomFamilyOptions<T, P>,\n): (params: P) => RecoilState<T> {\n  const { atomFamilies } = ledger;\n  const { key } = config;\n\n  // Initialize new family in atomFamilies tracker\n  atomFamilies[key] = {};\n\n  return (params: P): RecoilState<T> => {\n    const strParams = JSON.stringify(params);\n    // If the atom has already been created, return from cache, otherwise we'll be creating a new\n    // instance of an atom every time we invoke this func (which can lead to infinite re-render loop)\n    const cachedAtom = atomFamilies[key][strParams];\n    if (cachedAtom !== undefined) return cachedAtom;\n\n    const newAtomFamilyMember = recoilAtomFamily(config)(params);\n    // Storing every atom created except for dummy atom created by ChromogenObserver's onload useEffect hook\n    if (strParams !== dummyParam) atomFamilies[key][strParams] = newAtomFamilyMember;\n    return newAtomFamilyMember;\n  };\n}\n\n/* ----- SELECTOR FAMILY ----- */\nexport function selectorFamily<T, P extends SerializableParam>(\n  options: ReadWriteSelectorFamilyOptions<T, P>,\n): (param: P) => RecoilState<T>;\nexport function selectorFamily<T, P extends SerializableParam>(\n  options: ReadOnlySelectorFamilyOptions<T, P>,\n): (param: P) => RecoilValueReadOnly<T>;\n// Overload function signature\nexport function selectorFamily<T>(\n  config:\n    | ReadWriteSelectorFamilyOptions<T, SerializableParam>\n    | ReadOnlySelectorFamilyOptions<T, SerializableParam>,\n) {\n  const { key, get } = config;\n  const { transactions, selectorFamilies } = ledger;\n\n  // Testing whether returned function from configGet is async\n  if (\n    !get\n    || transactions.length > 0\n    || get(dummyParam).constructor.name === 'AsyncFunction'\n    || get(dummyParam)\n      .toString()\n      .match(/^\\s*return\\s*_.*\\.apply\\(this, arguments\\);$/m)\n  ) {\n    return recoilSelectorFamily(config);\n  }\n\n  const getter = wrapFamilyGetter(key, get);\n\n  const newConfig: SelectorFamilyConfig<any, SerializableParam> = { key, get: getter };\n\n  let isSettable = false;\n\n  if ('set' in config) {\n    isSettable = true;\n    const setter = wrapFamilySetter(key, config.set);\n    newConfig.set = setter;\n  }\n\n  // Create selector generator & add to selectorFamily for test setup\n  const trackedSelectorFamily = recoilSelectorFamily(newConfig);\n  selectorFamilies[key] = { trackedSelectorFamily, prevParams: new Set(), isSettable };\n  return trackedSelectorFamily;\n}\n"
  },
  {
    "path": "package/recoil_generator/src/api/core-utils.ts",
    "content": "/* eslint-disable */\nimport { debounce } from '../utils/utils';\nimport { ledger } from '../utils/ledger';\nimport { recordingState } from '../utils/store';\n/* eslint-enable */\n\nconst { transactions, initialRender, selectors, setTransactions } = ledger;\n\nconst DEBOUNCE_MS = 250;\n\n// Set timeout for selector get calls\nconst debouncedAddToTransactions = debounce(\n  (key, value, params) =>\n    params !== undefined\n      ? transactions[transactions.length - 1].familyUpdates.push({ key, value, params })\n      : transactions[transactions.length - 1].updates.push({ key, value }),\n  DEBOUNCE_MS,\n);\n\n// the logic for recording selectors only when they fire\n// whenever get method is fired, chromogen records\nconst wrapGetter = (key: string, get: Function) => {\n  let returnedPromise: boolean = false;\n\n  return (utils: any) => {\n    //will return what normal recoil selector will return aka regular selector method\n    const value = get(utils);\n\n    //Checking whether value is async\n    // Only capture selector data if currently recording (if record button has been hit)\n    if (utils.get(recordingState)) {\n      //making sure no transactions have been fired\n      if (transactions.length === 0) {\n        // Promise-validation is expensive, so we only do it once, on initial load\n        if (typeof value === 'object' && value !== null && value.constructor.name === 'Promise') {\n          ledger.selectors = selectors.filter((current) => current !== key);\n          returnedPromise = true;\n        } else {\n          initialRender.push({ key, value });\n        }\n      } else if (!returnedPromise) {\n        // Debouncing (throttling) allows TransactionObserver to push to array first\n        // Length must be computed within debounce to correctly find last transaction\n        // only capture meaningful function calls\n        // when called, timer starts; if x amount of time passes and function isnt called again, it fires; if called, resets timer\n        debouncedAddToTransactions(key, value);\n      }\n    }\n\n    return value;\n  };\n};\n\nconst wrapSetter = (key: string, set: Function) => (utils: any, newValue: any) => {\n  if (utils.get(recordingState) && setTransactions.length > 0) {\n    // allow TransactionObserver to push to array first\n    // Length must be computed after timeout to correctly find last transaction\n    // this is here b/c of async stuff with useRecoilTransactionObserver\n    setTimeout(() => {\n      setTransactions[setTransactions.length - 1].setter = { key, newValue };\n    }, 0);\n  }\n  // returns what regular selector would return (?)\n  return set(utils, newValue);\n};\n\nexport {debouncedAddToTransactions, wrapGetter, wrapSetter};"
  },
  {
    "path": "package/recoil_generator/src/api/family-utils.ts",
    "content": "/* eslint-disable */\nimport type { SerializableParam } from 'recoil';\n\nimport { ledger } from '../utils/ledger';\nimport { dummyParam } from '../utils/utils';\nimport { recordingState } from '../utils/store';\nimport { debouncedAddToTransactions } from './core-utils';\n/* eslint-enable */\n\nconst { transactions, selectorFamilies, initialRenderFamilies, setTransactions } = ledger;\n\nexport const wrapFamilyGetter = (key: string, configGet: Function) => {\n  let returnedPromise = false;\n\n  return (params: SerializableParam) => (utils: any) => {\n    const { get } = utils;\n    const value = configGet(params)(utils);\n    // Only capture selector data if currently recording\n\n    if (get(recordingState)) {\n      if (transactions.length === 0) {\n        // Promise-validation is expensive, so we only do it once, on initial load\n        if (\n          typeof value === 'object'\n          && value !== null\n          && Object.prototype.toString.call(value) === '[object Promise]'\n        ) {\n          delete selectorFamilies[key];\n          returnedPromise = true;\n        } else {\n          initialRenderFamilies.push({ key, params, value });\n        }\n      } else if (!returnedPromise) {\n        // Track every new params\n        if (!selectorFamilies[key].prevParams.has(params)) {\n          selectorFamilies[key].prevParams.add(params);\n        }\n        // Debouncing allows TransactionObserver to push to array first\n        // Length must be computed within debounce to correctly find last transaction\n        // Excluding dummy selector created by ChromogenObserver's onload useEffect hook\n        if (params !== dummyParam) debouncedAddToTransactions(key, value, params);\n      }\n    }\n\n    // Return value from original get method\n    return value;\n  };\n};\n\nexport const wrapFamilySetter = (key: string, set: Function) => (params: SerializableParam) => (\n  utils: any,\n  newValue: any,\n) => {\n  if (utils.get(recordingState) && setTransactions.length > 0) {\n    // allow TransactionObserver to push to array first\n    // Length must be computed after timeout to correctly find last transaction\n    setTimeout(() => {\n      setTransactions[setTransactions.length - 1].setter = { key, params, newValue };\n    }, 0);\n  }\n  return set(params)(utils, newValue);\n};\n"
  },
  {
    "path": "package/recoil_generator/src/component/ChromogenObserver.tsx",
    "content": "/* eslint-disable */\nimport type { Snapshot } from 'recoil';\nimport type { AtomFamilyState } from '../types';\n\nimport React, { useState, useEffect } from 'react';\nimport { useRecoilState, useRecoilTransactionObserver_UNSTABLE } from 'recoil';\nimport { dummyParam } from '../utils/utils';\nimport { recordingState } from '../utils/store';\nimport { ledger } from '../utils/ledger';\nimport { styles, generateFile, generateTests } from './component-utils';\n/* eslint-enable */\n\nexport const ChromogenObserver: React.FC<{ store?: Array<object> | object }> = ({ store }) => {\n  // Initializing as undefined over null to match React typing for AnchorHTML attributes\n  const [file, setFile] = useState<undefined | string>(undefined);\n  const [storeMap, setStoreMap] = useState<Map<string, string>>(new Map());\n  const [recording, setRecording] = useRecoilState<boolean>(recordingState);\n  const [devtool, setDevtool] = useState<boolean>(false);\n  const [, setEditFile] = useState<undefined | string>(undefined);\n\n  // DevTool message handling\n  const receiveMessage = (message: any) => {\n    switch (message.data.action) {\n      case 'connectChromogen':\n        setDevtool(true);\n        window.postMessage({ action: 'moduleConnected' }, '*');\n        break;\n      case 'downloadFile':\n        generateFile(setFile, storeMap);\n        break;\n      case 'editFile':\n        const array = generateFile(setEditFile, storeMap);\n        window.postMessage({ action: 'editFileReceived', data: array }, '*');\n        break;\n      case 'toggleRecord':\n        setRecording(!recording);\n        window.postMessage({ action: 'setStatus' }, '*');\n        break;\n      default:\n      // Do nothing\n    }\n  };\n\n  // Add/remove DevTool event listeners\n  useEffect(() => {\n    window.addEventListener('message', receiveMessage);\n\n    return () => window.removeEventListener('message', receiveMessage);\n  });\n\n  // Auto-click download link when a new file is generated (via button click)\n  useEffect(() => document.getElementById('chromogen-download')!.click(), [file]);\n  // ! to get around strict null check in tsconfig\n\n  // Update storeMap with src variable names if store prop passed\n  useEffect(() => {\n    if (store !== undefined) {\n      const storeArr = Array.isArray(store) ? store : [store];\n      const newStore: Map<string, string> = new Map();\n\n      storeArr.forEach((storeModule) => {\n        Object.entries(storeModule).forEach(([variable, imported]: [any, any]) => {\n          let key;\n          /** Relevant imports will be either an object (for vanilla atoms or selectors)\n           * or functions (for atom or selector families). If we are examining a family function,\n           * we will need to invoke it to create an atom/selector in order to pull the\n           * original family key out from the generated atom or selector's individual key.\n           * */\n          if (typeof imported === 'function') {\n            // Extended atom fam key will follow format of `[key]__\"chromogenDummyParam\"__withFallback`\n            // Extended selector fam key will follow format of `[key]__selectorFamily/\"chromogenDummyParam\"/1`\n            const extendedKey = imported(dummyParam).key;\n            key = extendedKey.includes('selectorFamily')\n              ? extendedKey.substring(0, extendedKey.indexOf('selectorFamily') - 2)\n              : extendedKey.substring(0, extendedKey.indexOf(`\"${dummyParam}\"`) - 2);\n          } else {\n            key = imported.key;\n          }\n          newStore.set(key, variable);\n        });\n      });\n      setStoreMap(newStore);\n    }\n  }, []);\n\n  useRecoilTransactionObserver_UNSTABLE(\n    ({ previousSnapshot, snapshot }: { previousSnapshot: Snapshot; snapshot: Snapshot }): void => {\n      // Map current snapshot to array of atom states\n      // Can't directly check recording hook b/c TransactionObserver runs before state update\n      if (snapshot.getLoadable(recordingState).contents) {\n        const { transactions, setTransactions, atoms, atomFamilies } = ledger;\n\n        const state = atoms.map((item) => {\n          const { key } = item;\n          const value = snapshot.getLoadable(item).contents;\n          const previous = previousSnapshot.getLoadable(item).contents;\n          const updated = value !== previous;\n          return { key, value, previous, updated };\n        });\n\n        const atomFamilyState: AtomFamilyState[] = [];\n\n        /* eslint-disable */\n        // TODO: refactor out of for-in syntax b/c for-in tracks up the prototype chain x_x\n        for (const family in atomFamilies) {\n          const familyMembers = atomFamilies[family];\n          for (const member in familyMembers) {\n            const memberRecoilState = familyMembers[member];\n            let { key } = memberRecoilState;\n            /* Key will be auto-generated by recoil in the format of\n             * [atomFamilyName] + \"__\" + [params] + \"__withFallback\".\n             * Removing the \"__withFallback\" suffix to enhance readability\n             */\n            key = key.substring(0, key.length - 14);\n            const value = snapshot.getLoadable(memberRecoilState).contents;\n            const previous = previousSnapshot.getLoadable(memberRecoilState).contents;\n            const updated = value !== previous;\n            // Don't track dummy atom generated by onload useEffect hook\n            if (!key.includes(dummyParam)) atomFamilyState.push({ family, key, value, updated });\n          }\n        }\n        /* eslint-enable */\n\n        transactions.push({ state, updates: [], atomFamilyState, familyUpdates: [] });\n        setTransactions.push({ state, setter: null });\n      }\n    },\n  );\n\n  const [pauseColor, setPauseColor] = useState('#90d1f0');\n  const pauseBorderStyle = {\n    borderColor: `${pauseColor}`,\n  };\n\n  const [playColor, setPlayColor] = useState('transparent transparent transparent #90d1f0')\n  const playBorderStyle = {\n    borderColor: `${playColor}`,\n  };\n\n  return (\n    <>\n      {\n        // Render button div only if DevTool not connected\n        !devtool && (\n          <div>\n            <div style={styles.divStyle}>\n              <button\n                aria-label={recording ? 'pause' : 'record'}\n                id=\"chromogen-toggle-record\"\n                style={{ ...styles.buttonStyle, backgroundColor: '#7f7f7f' }}\n                type=\"button\"\n                onClick={() => {\n                  setRecording(!recording);\n                  // if (!recording) return true;\n                  // return false;\n                }}\n                onMouseEnter={() => recording ? setPauseColor('#f6f071') : setPlayColor('transparent transparent transparent #f6f071')}\n                onMouseLeave={() => recording ? setPauseColor('#90d1f0') : setPlayColor('transparent transparent transparent #90d1f0')}\n              ><a>{recording ?\n                <div style={{ ...styles.pauseStyle, ...pauseBorderStyle }}></div>\n                : <div style={{ ...styles.playStyle, ...playBorderStyle }}></div>\n              }</a>\n              </button>\n              <button\n                aria-label=\"capture test\"\n                id=\"chromogen-generate-file\"\n                style={{ ...styles.buttonStyle, backgroundColor: '#7f7f7f', marginLeft: '-2px', marginRight: '13px' }}\n                type=\"button\"\n                onClick={() => generateFile(setFile, storeMap)}\n                onMouseEnter={() => document.getElementById(\"chromogen-generate-file\")!.style.color = '#f6f071'}\n                onMouseLeave={() => document.getElementById(\"chromogen-generate-file\")!.style.color = '#90d1f0'}\n              ><a>{'Download'}</a>\n              </button>\n              <button\n                aria-label=\"copy test\"\n                id=\"chromogen-copy-test\"\n                style={{ ...styles.buttonStyle, backgroundColor: '#7f7f7f', marginLeft: '-2px', marginRight: '13px' }}\n                type=\"button\"\n                onClick={() => { navigator.clipboard.writeText(generateTests(storeMap)[0]) }}\n                onMouseEnter={() => document.getElementById(\"chromogen-copy-test\")!.style.color = '#f6f071'}\n                onMouseLeave={() => document.getElementById(\"chromogen-copy-test\")!.style.color = '#90d1f0'}\n              ><a>{'Copy To Clipboard'}</a>\n              </button>\n            </div>\n          </div>\n        )\n      }\n      <a\n        download=\"chromogen.test.js\"\n        href={file}\n        id=\"chromogen-download\"\n        style={{ display: 'none' }}\n      >\n        Download Test\n      </a>\n    </>\n  );\n};\n"
  },
  {
    "path": "package/recoil_generator/src/component/component-utils.ts",
    "content": "/* eslint-disable */\nimport type { CSSProperties } from 'react';\nimport type { SerializableParam } from 'recoil';\nimport type { Ledger } from '../types';\n\nimport { ledger } from '../utils/ledger';\nimport { convertFamilyTrackerKeys } from '../utils/utils';\nimport { output } from '../output/output';\n/* eslint-enable */\n\nconst buttonStyle: CSSProperties = {\n  display: 'inline-block',\n  margin: '8px',\n  marginLeft: '13px',\n  padding: '0px',\n  height: '25px',\n  width: '65px',\n  borderRadius: '4px',\n  justifyContent: 'space-evenly',\n  border: '1px',\n  cursor: 'pointer',\n  color: '#90d1f0',\n  fontSize: '10px',\n};\n\nconst divStyle: CSSProperties = {\n  display: 'flex',\n  position: 'absolute',\n  bottom: '100px',\n  left: '100px',\n  backgroundColor: '#aaa',\n  borderRadius: '4px',\n  margin: 0,\n  padding: 0,\n  zIndex: 999999,\n};\n\nconst playStyle: CSSProperties = {\n  boxSizing: 'border-box',\n  marginLeft: '25px',\n  borderStyle: 'solid',\n  borderWidth: '7px 0px 7px 14px',\n};\n\nconst pauseStyle: CSSProperties = {\n  width: '14px',\n  height: '14px',\n  borderWidth: '0px 0px 0px 10px',\n  borderStyle: 'double',\n  marginLeft: '27px',\n};\n\nexport const styles = { buttonStyle, divStyle, playStyle, pauseStyle };\n\n/**\n * onclick function that generates test file & sets download URL\n *\n * Key-to-Variable name mapping is applied if storeMap has any contents\n * (meaning atom / selector nodes were passed as props)\n * Applying only at point-of-download keeps performance cost low for users who\n * don't need to pass nodes while creating a moderate performance hit for others\n * only while downloading, never while interacting with their app.\n */\nexport const generateTests = (storeMap: Map<string, string>): string[] => {\n  const {\n    atoms,\n    selectors,\n    setters,\n    atomFamilies,\n    selectorFamilies,\n    initialRender,\n    initialRenderFamilies,\n    transactions,\n    setTransactions,\n  } = ledger;\n\n  const finalLedger: Ledger<string, any, SerializableParam> =\n    storeMap.size > 0\n      ? {\n          atoms: atoms.map(({ key }) => storeMap.get(key) || key),\n          selectors: selectors.map((key) => storeMap.get(key) || key),\n          atomFamilies: convertFamilyTrackerKeys(atomFamilies, storeMap),\n          selectorFamilies: convertFamilyTrackerKeys(selectorFamilies, storeMap),\n          setters: setters.map((key) => storeMap.get(key) || key),\n          initialRender: initialRender.map(({ key, value }) => {\n            const newKey = storeMap.get(key) || key;\n            return { key: newKey, value };\n          }),\n          initialRenderFamilies: initialRenderFamilies.map(({ key, value, params }) => {\n            const newKey = storeMap.get(key) || key;\n            return { key: newKey, value, params };\n          }),\n          transactions: transactions.map(({ state, updates, atomFamilyState, familyUpdates }) => {\n            const newState = state.map((eachAtom) => {\n              const key = storeMap.get(eachAtom.key) || eachAtom.key;\n              return { ...eachAtom, key };\n            });\n            const newUpdates = updates.map((eachSelector) => {\n              const key = storeMap.get(eachSelector.key) || eachSelector.key;\n              const { value } = eachSelector;\n              return { key, value };\n            });\n            const newAtomFamilyState = atomFamilyState.map((eachFamAtom) => {\n              const family = storeMap.get(eachFamAtom.family) || eachFamAtom.family;\n              const oldKey = eachFamAtom.key;\n              const keySuffix = oldKey.substring(eachFamAtom.family.length);\n              const key = family + keySuffix;\n              return { ...eachFamAtom, family, key };\n            });\n            const newFamilyUpdates = familyUpdates.map((eachFamSelector) => {\n              const key = storeMap.get(eachFamSelector.key) || eachFamSelector.key;\n              return { ...eachFamSelector, key };\n            });\n            return {\n              state: newState,\n              updates: newUpdates,\n              atomFamilyState: newAtomFamilyState,\n              familyUpdates: newFamilyUpdates,\n            };\n          }),\n          setTransactions: setTransactions.map(({ state, setter }) => {\n            const newState = state.map((eachAtom) => {\n              const key = storeMap.get(eachAtom.key) || eachAtom.key;\n              return { ...eachAtom, key };\n            });\n            const newSetter = setter;\n            if (newSetter) {\n              const { key } = newSetter;\n              newSetter.key = storeMap.get(key) || key;\n            }\n            return { state: newState, setter: newSetter };\n          }),\n        }\n      : { ...ledger, atoms: atoms.map(({ key }) => key) };\n\n  //return setFile(URL.createObjectURL(new Blob([output(finalLedger)])));\n  return [output(finalLedger)];\n};\n\nexport const generateFile = (setFile: Function, storeMap: Map<string, string>): string[] => {\n  const tests = generateTests(storeMap);\n  const blob = new Blob(tests);\n  setFile(URL.createObjectURL(blob));\n  return tests;\n};\n"
  },
  {
    "path": "package/recoil_generator/src/output/output-utils.ts",
    "content": "/* eslint-disable */\nimport type {\n  SelectorUpdate,\n  Transaction,\n  AtomUpdate,\n  SetTransaction,\n  AtomFamilies,\n  SelectorFamilies,\n  SelectorFamilyUpdate,\n  SelectorFamilyMembers,\n} from '../types';\nimport { SerializableParam } from 'recoil';\n/* eslint-enable */\n\n/* ----- HELPER FUNCTIONS ----- */\n\nexport function initializeAtoms(state: AtomUpdate[], current: boolean): string {\n  return state.reduce(\n    (initializers, { key, value, previous }) =>\n      `${initializers}\\t\\t\\tresult.current.set${key}(${JSON.stringify(\n        current ? value : previous,\n      )});\\n\\n`,\n    '',\n  );\n}\n//chromogen only captures selectors that fire; this pulls value from latest firing\nexport function assertState(updates: SelectorUpdate[]): string {\n  return updates.reduce(\n    (assertions, { key, value }) =>\n      `${assertions}\\t\\texpect(result.current.${key}Value).toStrictEqual(${JSON.stringify(\n        value,\n      )});\\n\\n`,\n    '',\n  );\n}\n\n/* ----- SETUP FUNCTIONS ----- */\n\nexport function importRecoilState(keyArray: string[]): string {\n  return keyArray.reduce((fullStr, key) => `${fullStr}\\t${key},\\n`, '');\n}\n\nexport function importRecoilFamily(\n  familyObj: AtomFamilies | SelectorFamilies<any, SerializableParam>,\n): string {\n  return Object.keys(familyObj).reduce(\n    (importStr, familyName) => `${importStr}\\t${familyName},\\n`,\n    '',\n  );\n}\n\nexport function writeableHook(keyArray: string[]): string {\n  return keyArray.reduce(\n    (fullStr, key) => `${fullStr}\\tconst [${key}Value, set${key}] = useRecoilState(${key});\\n`,\n    '',\n  );\n}\n\nexport function readableHook(keyArray: string[]): string {\n  return keyArray.reduce(\n    (fullStr, key) => `${fullStr}\\tconst ${key}Value = useRecoilValue(${key});\\n`,\n    '',\n  );\n}\n\nexport function atomFamilyHook(transactionArray: Transaction[]): string {\n  const len = transactionArray.length;\n  return len\n    ? transactionArray[len - 1].atomFamilyState.reduce((str, atomState) => {\n        const { family, key } = atomState;\n        /* Removing all special characters from string: if the params are passed\n         * in as a string, then we need to remove any special characters so they don't\n         * error out variable name generation, also recoil will add escaped quotes\n         * to the key name if it's a string, so will need to remove those by default\n         */\n        const params = key.substring(family.length + 2);\n        const scrubbedParams = params.replace(/[^\\w\\s]/gi, '');\n\n        const parsedParams = JSON.parse(params);\n\n        return `${str}\\tconst [${`${family}__${scrubbedParams}__Value`}, ${`set${family}__${scrubbedParams}`}] = useRecoilState(${family}(${\n          typeof parsedParams === 'string' ? `${params}` : `${parsedParams}`\n        }));\\n`;\n      }, '')\n    : '';\n}\n\nexport function selectorFamilyHook(\n  selectorFamilyTracker: SelectorFamilies<any, SerializableParam>,\n  isSettable: boolean,\n): string {\n  return Object.entries(selectorFamilyTracker)\n    .filter((familyArr) => familyArr[1].isSettable === isSettable)\n    .reduce((str: string, familyArr: [string, { prevParams: Set<any> }]): string => {\n      const [familyName, { prevParams }] = familyArr;\n      // converting prevParams from set to array\n      return `${str}${[...prevParams].reduce((innerStr: string, param: any) => {\n        let scrubbedParams;\n        if (typeof param === 'string') {\n          scrubbedParams = param.replace(/[^\\w\\s]/gi, '');\n        }\n\n        return isSettable\n          ? `${innerStr}\\tconst [${`${familyName}__${\n              scrubbedParams !== undefined ? scrubbedParams : param\n            }__Value`}, ${`set${familyName}__${\n              scrubbedParams !== undefined ? scrubbedParams : param\n            }`}] = useRecoilState(${familyName}(${\n              typeof param === 'string' ? `\"${param}\"` : `${JSON.parse(param)}`\n            }));\\n`\n          : `${innerStr}\\tconst ${`${familyName}__${\n              scrubbedParams !== undefined ? scrubbedParams : param\n            }__Value`} = useRecoilValue(${familyName}(${\n              typeof param === 'string' ? `\"${param}\"` : `${JSON.parse(param)}`\n            }));\\n`;\n      }, '')}`;\n    }, '');\n}\n\nexport function returnWriteable(keyArray: string[]): string {\n  return keyArray.reduce((fullStr, key) => `${fullStr}\\t\\t${key}Value,\\n\\t\\tset${key},\\n`, '');\n}\n\nexport function returnReadable(keyArray: string[]): string {\n  return keyArray.reduce((fullStr, key) => `${fullStr}\\t\\t${key}Value,\\n`, '');\n}\n\nexport function returnAtomFamily(transactionArray: Transaction[]): string {\n  const len = transactionArray.length;\n  return len\n    ? transactionArray[len - 1].atomFamilyState.reduce((value, atomState) => {\n        const { family, key } = atomState;\n        // key will be \"[familyname]__[params]\"\n        const params = key.substring(family.length + 2);\n        const scrubbedParams = params.replace(/[^\\w\\s]/gi, '');\n        return `${value}\\t\\t${`${family}__${scrubbedParams}__Value`},\n      \\t\\t${`set${family}__${scrubbedParams}`},\\n`;\n      }, '')\n    : '';\n}\n\nexport function returnSelectorFamily(\n  selectorFamilyTracker: SelectorFamilies<any, SerializableParam>,\n  isSettable: boolean,\n) {\n  return Object.entries(selectorFamilyTracker)\n    .filter((familyArr) => familyArr[1].isSettable === isSettable)\n    .reduce(\n      (str: string, familyArr: [string, SelectorFamilyMembers<any, SerializableParam>]): string => {\n        const [familyName, { prevParams }] = familyArr;\n        if (isSettable) {\n          return `${str}${[...prevParams].reduce((innerStr: string, param: any) => {\n            let scrubbedParams;\n            if (typeof param === 'string') {\n              scrubbedParams = param.replace(/[^\\w\\s]/gi, '');\n            }\n\n            return `${innerStr}\\t\\t${`${familyName}__${\n              scrubbedParams !== undefined ? scrubbedParams : param\n            }__Value`},\n            ${`set${familyName}__${scrubbedParams !== undefined ? scrubbedParams : param}`},\\n`;\n          }, '')}`;\n        }\n        return `${str}${[...prevParams].reduce((innerStr: string, param: any) => {\n          let scrubbedParams;\n          if (typeof param === 'string') {\n            scrubbedParams = param.replace(/[^\\w\\s]/gi, '');\n          }\n          return `${innerStr}\\t\\t${`${familyName}__${\n            scrubbedParams !== undefined ? scrubbedParams : param\n          }__Value`},\\n`;\n        }, '')}`;\n      },\n      '',\n    );\n}\n\n/* ----- INITIAL RENDER ----- */\n\nexport function initializeSelectors(initialRender: SelectorUpdate[]): string {\n  return initialRender.reduce(\n    (fullStr, { key, value }) => `${fullStr}\\tit('${key} should initialize correctly', () => {\n\\t\\texpect(result.current.${key}Value).toStrictEqual(${JSON.stringify(value)});\n\\t});\\n\\n`,\n    '',\n  );\n}\n\nexport function initializeSelectorFamilies(initialRenderFamilies: SelectorFamilyUpdate[]) {\n  return initialRenderFamilies.reduce((initialTests, { key, params, value }) => {\n    let scrubbedParams;\n    if (typeof params === 'string') {\n      scrubbedParams = params.replace(/[^\\w\\s]/gi, '');\n    }\n\n    return `${initialTests}\\tit('${key}__${\n      scrubbedParams !== undefined ? scrubbedParams : JSON.stringify(params)\n    } should initialize correctly', () => {\n    \\t\\texpect(result.current.${key}__${\n      scrubbedParams !== undefined ? scrubbedParams : JSON.stringify(params)\n    }__Value).toStrictEqual(${JSON.stringify(value)});\n    \\t});\\n`;\n  }, '');\n}\n\n/* ----- SELECTORS TEST ----- */\n//checking get methods\nexport function testSelectors(transactionArray: Transaction[]): string {\n  return transactionArray.reduce(\n    (selectorTests, { state, updates, atomFamilyState, familyUpdates }) => {\n      //checking to make sure chromogen doesn't look at atoms that haven't changed state\n      const allUpdatedAtoms = [\n        ...state.filter(({ updated }) => updated),\n        ...atomFamilyState.filter(({ updated }) => updated),\n      ];\n      const allUpdatedSelectors: any[] = [...updates, ...familyUpdates];\n      const atomLen = allUpdatedAtoms.length;\n      const selectorLen = allUpdatedSelectors.length;\n\n      return atomLen !== 0 && selectorLen !== 0\n        ? `${selectorTests}\\tit('${\n            selectorLen > 1\n              ? allUpdatedSelectors.reduce((list, selectorState, i) => {\n                  const { key } = selectorState;\n                  const isLastElement = i === selectorLen - 1;\n                  // if params exist, then we are looking at a selectorFamily\n                  if ('params' in selectorState) {\n                    let scrubbedParams;\n                    if (typeof selectorState.params === 'string') {\n                      scrubbedParams = selectorState.params.replace(/[^\\w\\s]/gi, '');\n                    }\n\n                    return `${list}${isLastElement ? 'and ' : ''}${key}__${\n                      scrubbedParams !== undefined ? scrubbedParams : selectorState.params\n                    }${isLastElement ? '' : ', '}`;\n                  }\n                  return `${list}${isLastElement ? 'and ' : ''}${key}${isLastElement ? '' : ', '}`;\n                }, '')\n              : `${\n                  allUpdatedSelectors[0].params !== undefined\n                    ? `${allUpdatedSelectors[0].key}__${\n                        typeof allUpdatedSelectors[0].params === 'string'\n                          ? allUpdatedSelectors[0].params.replace(/[^\\w\\s]/gi, '')\n                          : allUpdatedSelectors[0].params\n                      }`\n                    : allUpdatedSelectors[0].key\n                }`\n          } should properly derive state when ${\n            atomLen > 1\n              ? allUpdatedAtoms.reduce((list, { key }, i) => {\n                  const isLastElement = i === atomLen - 1;\n                  const scrubbedKey = key.replace(/[^\\w\\s]/gi, '');\n                  return `${list}${isLastElement ? 'and ' : ''}${scrubbedKey}${\n                    isLastElement ? ' update' : ', '\n                  }`;\n                }, '')\n              : `${allUpdatedAtoms[0].key.replace(/[^\\w\\s]/gi, '')} updates`\n          }', () => {\n\\t\\tconst { result } = renderRecoilHook(useStoreHook);\n\n\\t\\tact(() => {\n  ${state.reduce(\n    (initializers, { key, value }) =>\n      `${initializers}\\t\\t\\tresult.current.set${key}(${JSON.stringify(value)});\\n\\n`,\n    '',\n  )}\n  ${atomFamilyState.reduce((initializers, { key, value }) => {\n    const scrubbedKey = key.replace(/[^\\w\\s]/gi, '');\n    return `${initializers}\\t\\t\\tresult.current.set${scrubbedKey}(${JSON.stringify(value)});\\n\\n`;\n  }, '')}\n\\t\\t});\n${\n  selectorLen !== 0\n    ? allUpdatedSelectors.reduce((assertions, selectorState) => {\n        const { key, value } = selectorState;\n\n        let scrubbedParams;\n        if (typeof selectorState.params === 'string') {\n          scrubbedParams = selectorState.params.replace(/[^\\w\\s]/gi, '');\n        }\n\n        if (selectorState.params !== undefined)\n          return `${assertions}\\t\\texpect(result.current.${key}__${\n            scrubbedParams !== undefined ? scrubbedParams : selectorState.params\n          }__Value).toStrictEqual(${JSON.stringify(value)});\\n\\n`;\n        return `${assertions}\\t\\texpect(result.current.${key}Value).toStrictEqual(${JSON.stringify(\n          value,\n        )});\\n\\n`;\n      }, '')\n    : ''\n}\\t});\\n\\n`\n        : selectorTests;\n    },\n    '',\n  );\n}\n\n/* ----- SETTERS TEST ----- */\n\nexport function testSetters(setTransactionArray: SetTransaction[]): string {\n  return setTransactionArray.reduce((setterTests, { state, setter }) => {\n    const updatedAtoms = state.filter(({ updated }) => updated);\n\n    if (setter) {\n      const { params } = setter;\n\n      let scrubbedParams;\n      if (typeof params === 'string') {\n        scrubbedParams = params.replace(/[^\\w\\s]/gi, '');\n      }\n\n      return params !== undefined\n        ? `${setterTests}\\tit('${setter.key}__${\n            scrubbedParams !== undefined ? scrubbedParams : JSON.stringify(params)\n          } should properly set state', () => {\n        \\t\\tconst { result } = renderRecoilHook(useStoreHook);\n        \n        \\t\\tact(() => {\n        ${initializeAtoms(state, false)}\\t\\t});\n        \n        \\t\\tact(() => { \n        \\t\\t\\tresult.current.set${setter.key}__${\n            scrubbedParams !== undefined ? scrubbedParams : JSON.stringify(params)\n          }(${JSON.stringify(setter.newValue)});\n        \\t\\t});\n        \n        ${assertState(updatedAtoms)}\\t});\\n\\n`\n        : `${setterTests}\\tit('${setter.key} should properly set state', () => {\n          \\t\\tconst { result } = renderRecoilHook(useStoreHook);\n          \n          \\t\\tact(() => {\n          ${initializeAtoms(state, false)}\\t\\t});\n          \n          \\t\\tact(() => { \n          \\t\\t\\tresult.current.set${setter.key}(${JSON.stringify(setter.newValue)});\n          \\t\\t});\n          \n          ${assertState(updatedAtoms)}\\t});\\n\\n`;\n    }\n    return setterTests;\n  }, '');\n}\n"
  },
  {
    "path": "package/recoil_generator/src/output/output.ts",
    "content": "/* eslint-disable */\nimport type { Ledger } from '../types';\nimport type { SerializableParam } from 'recoil';\nimport {\n  importRecoilState,\n  writeableHook,\n  readableHook,\n  returnWriteable,\n  returnReadable,\n  testSelectors,\n  testSetters,\n  importRecoilFamily,\n  atomFamilyHook,\n  selectorFamilyHook,\n  returnSelectorFamily,\n  initializeSelectors,\n  returnAtomFamily,\n} from './output-utils';\n\n/* eslint-enable */\n\n/* ----- HELPERS ----- */\nexport const setFilter = (selectors: string[], setters: string[]): string[] =>\n  selectors.filter((key) => !setters.includes(key));\n\n/* ----- MAIN ----- */\nexport const output = ({\n  atoms,\n  selectors,\n  setters,\n  atomFamilies,\n  selectorFamilies,\n  initialRender,\n  transactions,\n  setTransactions,\n}: Ledger<string, any, SerializableParam>): string =>\n  `import { renderRecoilHook, act } from 'react-recoil-hooks-testing-library';\nimport { useRecoilValue, useRecoilState } from 'recoil';\nimport {\n${\n  importRecoilState(selectors)\n  + importRecoilFamily(selectorFamilies)\n}\n} from '<ADD STORE FILEPATH>';\nimport { \n${\n  importRecoilState(atoms)\n  + importRecoilFamily(atomFamilies)\n}\n} from '<ADD ATOM FILEPATH>';\n\n// Suppress 'Batcher' warnings from React / Recoil conflict\nconsole.error = jest.fn();\n\n// Hook to return atom/selector values and/or modifiers for react-recoil-hooks-testing-library\nconst useStoreHook = () => {\n  // atoms\n${writeableHook(atoms)}\n  // writeable selectors\n${writeableHook(setters)}\n  // read-only selectors\n${readableHook(setFilter(selectors, setters))}\n  // atom families\n${atomFamilyHook(transactions)}\n  // writeable selector families\n${selectorFamilyHook(selectorFamilies, true)}\n  // read-only selector families\n${selectorFamilyHook(selectorFamilies, false)}\n\n\n\n  return {\n${\n  returnWriteable(atoms)\n  + returnWriteable(setters)\n  + returnReadable(setFilter(selectors, setters))\n  + returnAtomFamily(transactions)\n  + returnSelectorFamily(selectorFamilies, true)\n  + returnSelectorFamily(selectorFamilies, false)\n}\\t};\n};\n\ndescribe('INITIAL RENDER', () => { \n  const { result } = renderRecoilHook(useStoreHook); \n  \n${initializeSelectors(initialRender)}\n});\n\ndescribe('SELECTORS', () => {\n${testSelectors(transactions)}});\n\ndescribe('SETTERS', () => {\n${testSetters(setTransactions)}});`;\n"
  },
  {
    "path": "package/recoil_generator/src/types.ts",
    "content": "/* eslint-disable */\nimport type {\n  RecoilState,\n  RecoilValue,\n  DefaultValue,\n  SerializableParam,\n  RecoilValueReadOnly,\n} from 'recoil';\n/* eslint-enable */\n\n// ----- INITIALIZING NON-IMPORTABLE RECOIL TYPES -----\ntype ResetRecoilState = (recoilVal: RecoilState<any>) => void;\n\ntype GetRecoilValue = <T>(recoilVal: RecoilValue<T>) => T;\n\ntype SetRecoilState = <T>(\n  recoilVal: RecoilState<T>,\n  newVal: T | DefaultValue | ((prevValue: T) => T | DefaultValue),\n) => void;\n\n// ----- EXPORTING TYPES TO BE USED IN SRC/.TSX FILES -----\nexport interface SetterUpdate {\n  key: string;\n  newValue: any;\n  params?: SerializableParam;\n}\n\nexport interface SelectorUpdate {\n  key: string;\n  value: any;\n}\n\nexport interface SelectorFamilyUpdate extends SelectorUpdate {\n  params: SerializableParam;\n}\n\nexport interface AtomUpdate extends SelectorUpdate {\n  previous: any;\n  updated: boolean;\n}\n\nexport interface AtomFamilyState {\n  family: string;\n  key: string;\n  value: any;\n  updated: boolean;\n}\n\nexport interface Transaction {\n  state: AtomUpdate[];\n  updates: SelectorUpdate[];\n  atomFamilyState: AtomFamilyState[];\n  familyUpdates: SelectorFamilyUpdate[];\n}\n\nexport interface SetTransaction {\n  state: AtomUpdate[];\n  setter: null | SetterUpdate;\n}\n\nexport interface AtomFamilyMembers {\n  [atomName: string]: RecoilState<any>;\n}\nexport interface AtomFamilies {\n  [familyName: string]: AtomFamilyMembers;\n}\n\nexport interface SelectorFamilyConfig<T, P extends SerializableParam> {\n  key: string;\n  get: (param: P) => (opts: { get: GetRecoilValue }) => Promise<T> | RecoilValue<T> | T;\n  set?: (\n    param: P,\n  ) => (\n    opts: { set: SetRecoilState; get: GetRecoilValue; reset: ResetRecoilState },\n    newValue: T | DefaultValue,\n  ) => void;\n  dangerouslyAllowMutability?: boolean;\n}\nexport interface SelectorFamilyMembers<T, P> {\n  trackedSelectorFamily: (param: P) => RecoilState<T> | RecoilValueReadOnly<T>;\n  isSettable: boolean;\n  prevParams: Set<any>;\n}\nexport interface SelectorFamilies<T, P> {\n  [familyName: string]: SelectorFamilyMembers<T, P>;\n}\n\n// atoms should take RecoilState<any>[] | string[]\nexport interface Ledger<T, S, P> {\n  atoms: T[];\n  selectors: string[];\n  atomFamilies: AtomFamilies;\n  selectorFamilies: SelectorFamilies<S, P>;\n  setters: string[];\n  initialRender: SelectorUpdate[];\n  initialRenderFamilies: SelectorFamilyUpdate[];\n  transactions: Transaction[];\n  setTransactions: SetTransaction[];\n}\n\nexport interface SelectorConfig<T> {\n  key: string;\n  get: (opts: { get: GetRecoilValue }) => T | Promise<T> | RecoilValue<T>;\n  set?: (\n    opts: { get: GetRecoilValue; set: SetRecoilState; reset: ResetRecoilState },\n    newValue: T | DefaultValue,\n  ) => void;\n  dangerouslyAllowMutability?: boolean;\n}\n"
  },
  {
    "path": "package/recoil_generator/src/utils/ledger.ts",
    "content": "/* eslint-disable */\nimport type { Ledger } from '../types';\nimport { RecoilState, SerializableParam } from 'recoil';\n/* eslint-enable */\n\nexport const ledger: Ledger<RecoilState<any>, any, SerializableParam> = {\n  atoms: [],\n  selectors: [], //get\n  atomFamilies: {},\n  selectorFamilies: {},\n  setters: [], //set\n  initialRender: [],\n  initialRenderFamilies: [],\n  transactions: [],//get\n  setTransactions: [],//set\n};\n"
  },
  {
    "path": "package/recoil_generator/src/utils/store.ts",
    "content": "/* eslint-disable */\nimport type { RecoilState } from 'recoil';\nimport { atom } from 'recoil';\n/* eslint-enable */\n\n// Recording toggle\nexport const recordingState: RecoilState<boolean> = atom<boolean>({\n  key: 'recordingState',\n  default: true,\n});\n"
  },
  {
    "path": "package/recoil_generator/src/utils/utils.ts",
    "content": "/* eslint-disable */\nimport type { SerializableParam } from 'recoil';\nimport type { AtomFamilies, SelectorFamilies } from '../types';\n/* eslint-enable */\n\n// Debouncing for selector transaction updates\nexport const debounce = (func: (...args: any[]) => any, wait: number) => {\n  let timeout: any;\n\n  return (...args: any[]) => {\n    const timeoutCallback = () => {\n      timeout = null;\n      func(...args);\n    };\n\n    clearTimeout(timeout);\n    timeout = setTimeout(timeoutCallback, wait);\n  };\n};\n\n// Used in key-to-variable name mapping in generateFile\nexport function convertFamilyTrackerKeys(\n  familyTracker: AtomFamilies,\n  storeMap: Map<string, string>,\n): AtomFamilies;\nexport function convertFamilyTrackerKeys<T, P extends SerializableParam>(\n  familyTracker: SelectorFamilies<T, P>,\n  storeMap: Map<string, string>,\n): SelectorFamilies<T, P>;\n\nexport function convertFamilyTrackerKeys(\n  familyTracker: AtomFamilies | SelectorFamilies<any, SerializableParam>,\n  storeMap: Map<string, string>,\n) {\n  const refactoredTracker: AtomFamilies | SelectorFamilies<any, SerializableParam> = {};\n\n  Object.keys(familyTracker).forEach((key) => {\n    const newKey: string = storeMap.get(key) || key;\n    refactoredTracker[newKey] = familyTracker[key];\n  });\n\n  return refactoredTracker;\n}\n\n// Dummy param for use in various checks (most notably the key-to-variable name mapping)\nexport const dummyParam = 'chromogenDummyParam';\n"
  },
  {
    "path": "package/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES6\" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,\n    \"module\": \"commonjs\" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,\n    \"jsx\": \"react\" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,\n    \"declaration\": true /* Generates corresponding '.d.ts' file. */,\n    \"outDir\": \"./build\" /* Redirect output structure to the directory. */,\n    \"rootDir\": \".\" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,\n    \"removeComments\": true /* Do not emit comments to output. */,\n    \"strict\": true /* Enable all strict type-checking options. */,\n    \"noImplicitAny\": false /* Raise error on expressions and declarations with an implied 'any' type. */,\n    \"strictNullChecks\": true /* Enable strict null checks. */,\n    \"strictFunctionTypes\": true /* Enable strict checking of function types. */,\n    \"strictBindCallApply\": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */,\n    \"strictPropertyInitialization\": true /* Enable strict checking of property initialization in classes. */,\n    \"noImplicitThis\": true /* Raise error on 'this' expressions with an implied 'any' type. */,\n    \"noUnusedLocals\": true /* Report errors on unused locals. */,\n    \"noUnusedParameters\": true /* Report errors on unused parameters. */,\n    \"noImplicitReturns\": true /* Report error when not all code paths in function return a value. */,\n    \"noFallthroughCasesInSwitch\": true /* Report errors for fallthrough cases in switch statement. */,\n    \"moduleResolution\": \"node\" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,\n    \"baseUrl\": \"./\" /* Base directory to resolve non-absolute module names. */,\n    \"esModuleInterop\": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,\n    \"forceConsistentCasingInFileNames\": true /* Disallow inconsistently-cased references to the same file. */\n  },\n  // \"include\": [\"package/**/*\"]\n}"
  },
  {
    "path": "package/zustand_generator/__tests__/api.test.js",
    "content": "import { ledger } from '../src/utils/ledger';\nimport { chromogenZustandMiddleware } from '../src/api/api';\nimport { renderHook, act } from '@testing-library/react';\nimport create from 'zustand';\n\n// testing chromogenZustandMiddleware\ndescribe('chromogenZustandMiddleware', () => {\n  // destructuring atoms from ledger interface in utils folder\n  it('is a function', () => {\n    expect(typeof chromogenZustandMiddleware).toBe('function');\n  });\n\n  it('should update ledger upon invocation', () => {\n    // creating a mock store\n    const useStore = create(\n      chromogenZustandMiddleware((set) => ({\n        count: 0,\n        increment: () => {\n          set((state) => ({ count: count + 1 }), false, 'increment');\n        },\n      })),\n    );\n    //rendering the useStore Hook\n    const { result } = renderHook(useStore);\n\n    // verifying atoms property (array) on ledger has been updated with input atom\n    expect(result.current.count).toStrictEqual(0);\n    expect(ledger.initialRender).toStrictEqual({ count: 0 });\n  });\n});\n"
  },
  {
    "path": "package/zustand_generator/__tests__/output-utils.test.js",
    "content": "import {\n  testInitialState,\n  testStateChangesExpect,\n  testStateChangesAct,\n  generateActLine\n} from '../src/output/output-utils';\n\nconst initialRender = {\n  todoListState: [],\n  todoListFilterState: 'Show All',\n  todoListSortState: false,\n  quoteText: '',\n  quoteNumber: 0,\n  checkBox: false,\n}\n\ndescribe('INITIAL RENDER', () => {\n  //create a variable to hold our expected output\n  const expectedOutput = ''\n    + `\\tit('todoListState should initialize correctly', () => {\\n\\t\\texpect(result.current.todoListState).toStrictEqual([]);\\n\\t});\\n\\n`\n    + `\\tit('todoListFilterState should initialize correctly', () => {\\n\\t\\texpect(result.current.todoListFilterState).toStrictEqual(\"Show All\");\\n\\t});\\n\\n`\n    + `\\tit('todoListSortState should initialize correctly', () => {\\n\\t\\texpect(result.current.todoListSortState).toStrictEqual(false);\\n\\t});\\n\\n`\n    + `\\tit('quoteText should initialize correctly', () => {\\n\\t\\texpect(result.current.quoteText).toStrictEqual(\"\");\\n\\t});\\n\\n`\n    + `\\tit('quoteNumber should initialize correctly', () => {\\n\\t\\texpect(result.current.quoteNumber).toStrictEqual(0);\\n\\t});\\n\\n`\n    + `\\tit('checkBox should initialize correctly', () => {\\n\\t\\texpect(result.current.checkBox).toStrictEqual(false);\\n\\t});\\n\\n`\n  //create a variable and assign it to the evaluated result of the calling the testInitialState on our input\n  const evaluatedResult = testInitialState(initialRender);\n  //expect(realOutput).toStrictEqual(expectedOutput);\n  it('expectedOutput should equal evaulatedResult', () => {\n    expect(evaluatedResult).toStrictEqual(expectedOutput);\n  });\n\n\n\n})\n\ndescribe('TEST STATE CHANGES ACT', () => {\n\n  const transaction = [\n    {\n      action: 'setFilter',\n      arguments: ['Show Uncompleted'],\n      changedValues: { 'todoListFilterState': 'Show Uncompleted' }\n    }\n  ];\n  const testStateChangesActOutput = testStateChangesAct(transaction);\n\n  const expectedOutput =\n\n    `\\n\\tit('todoListFilterState should update correctly', () => {\n      const { result } = renderHook(useStore);\n\n      act(() => {\\n  result.current.setFilter(\"Show Uncompleted\");\\n});\n\n    \\nexpect(result.current.todoListFilterState).toStrictEqual(\"Show Uncompleted\");  \n      });`;\n\n  let expectedNoWhitespace = expectedOutput.replace(/\\s/g, '');\n\n\n  it('expect output to equal expected output', () => {\n    expect(testStateChangesActOutput.replace(/\\s/g, '')).toStrictEqual(expectedNoWhitespace)\n  })\n})\n\n\n\ndescribe('TEST STATE CHANGES EXPECT', () => {\n  //1. Create function inputs manually\n  const input = [\"todoListFilterState\", \"Show Completed\"];\n  //2. Manually write out expected output of function\n  const expectedOutput = `\\nexpect(result.current.todoListFilterState).toStrictEqual(\"Show Completed\");`\n  //3. Run function to get actual output\n  //4. Compare exptected ouptut to actual output\n  const testStateChanges = testStateChangesExpect(input)\n\n  it('it should be true when when input is passed into testStateChanges function ', () => {\n    expect(testStateChanges).toStrictEqual(expectedOutput);\n  })\n})\n\n\n\n\ndescribe('GENERATE ACT LINE', () => {\n  // Create a tranaction inputs manually\n  const transaction = {\n    action: 'setFilter',\n    arguments: ['Show Completed'],\n    changedValues: { todoListFilterState: \"Show Completed\" }\n  };\n  const action = transaction.action;\n  const args = transaction.arguments;\n\n  const expectedOutput = `\\tresult.current.${action}(${args?.map(arg => JSON.stringify(arg)).join(', ')});\\n`\n\n  const testActGeneration = generateActLine(transaction)\n  // Manually writing out expected output of function\n  //const evaluateActGeneration = `\\tresult.current.${action}(${args?.map(arg => JSON.stringify(arg)).join(', ')});\\n`\n  // Run func to get actual ouput\n  //Compare  step2 to step 3\n  it('testActGeneration should generate an action line', () => {\n    expect(expectedOutput).toStrictEqual(testActGeneration)\n  })\n})"
  },
  {
    "path": "package/zustand_generator/src/GlobalStyle.ts",
    "content": "/* Reset CSS section */\nimport { createGlobalStyle } from 'styled-components';\n\nconst GlobalStyle = createGlobalStyle`\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n\n* {\n  margin: 0;\n}\n\nhtml,\nbody,\n#root // for create-react-app  \n{\n  height: 100%;\n}\n\nimg,\npicture,\nvideo,\ncanvas,\nsvg {\n  display: block;\n  max-width: 100%;\n}\n\ninput,\nbutton,\ntextarea,\nselect {\n  font: inherit;\n}\n\np,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  overflow-wrap: break-word;\n}\n\nh2 {\n  font-size: 36px;\n}\n\n#root,\n#__next {\n  isolation: isolate;\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    --background-default: #222;\n    --background-body: #161616;\n    --background-button: #dbdbdb;\n    --background-button-hover: #fafafa;\n    --card-background: #323232;\n    --card-border: #000;\n    --input-background: #333;\n    --input-border: #222;\n    --input-border-hover: #222;\n    --font-default: #ffffff;\n    --font-secondary: #8e8e8e;\n    --font-tertiary: #666;\n    --font-button: #161616;\n    --font-link: rgb(0, 149, 246);\n    --font-error: rgba(220, 70, 70, .6);\n    --background-error: rgba(220, 70, 70, .3);\n  }\n}\n\n@media (prefers-color-scheme: light) {\n  :root {\n    --background-default: #fff;\n    --background-body: #fafafa;\n    --background-button: #323232;\n    --background-button-hover: #161616;\n    --card-background: #fff;\n    --card-border: #dbdbdb;\n    --input-background: #eee;\n    --input-border: #dbdbdb;\n    --input-border-hover: #191919c2;\n    --font-default: #191919;\n    --font-secondary: #8e8e8e;\n    --font-tertiary: #8e8e8e;\n    --font-button: #fafafa;\n    --font-link: rgb(0, 149, 246);\n    --font-error: rgba(220, 70, 70, .6);\n    --background-error: rgba(220, 70, 70, .3);\n  }\n}\n\nbody {\n  margin: 0;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n    \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n    sans-serif;\n  box-sizing: border-box;\n}\n\n#app {\n  height: 100%;\n  width: 100%;\n}\n`;\n\nexport default GlobalStyle;\n"
  },
  {
    "path": "package/zustand_generator/src/api/api.ts",
    "content": "import { ledger } from '../utils/ledger';\nimport { Transaction, InitialRender } from '../types';\nimport { StoreApi, StateCreator, StoreMutatorIdentifier } from 'zustand';\n\n// Referenced Zustand First Party Middleware for Type implementation\n// See here: https://github.com/pmndrs/zustand/tree/main/src/middleware\n\ntype Chromogen = <\n  T extends unknown,\n  Mps extends [StoreMutatorIdentifier, unknown][] = [],\n  Mcs extends [StoreMutatorIdentifier, unknown][] = [],\n>(\n  creatorFunction: StateCreator<T, Mps, Mcs>,\n) => StateCreator<T, Mps, Mcs>;\n\ntype ChromogenImpl = <T extends unknown>(\n  creatorFunction: PopArgument<StateCreator<T, [], []>>,\n) => PopArgument<StateCreator<T, [], []>>;\n\ntype PopArgument<T extends (...a: never[]) => unknown> = T extends (\n  ...a: [...infer A, infer _]\n) => infer R\n  ? (...a: A) => R\n  : never;\n\ntype TakeTwo<T> = T extends []\n  ? [undefined, undefined]\n  : T extends [unknown]\n  ? [...a0: T, a1: undefined]\n  : T extends [unknown?]\n  ? [...a0: T, a1: undefined]\n  : T extends [unknown, unknown]\n  ? T\n  : T extends [unknown, unknown?]\n  ? T\n  : T extends [unknown?, unknown?]\n  ? T\n  : T extends [infer A0, infer A1, ...unknown[]]\n  ? [A0, A1]\n  : T extends [infer A0, (infer A1)?, ...unknown[]]\n  ? [A0, A1?]\n  : T extends [(infer A0)?, (infer A1)?, ...unknown[]]\n  ? [A0?, A1?]\n  : never;\n\ntype StoreDevtools<S> = S extends {\n  setState: (...a: infer Sa) => infer Sr;\n}\n  ? {\n      setState<A extends string | { type: unknown }>(...a: [...a: TakeTwo<Sa>, action?: A]): Sr;\n    }\n  : never;\n\ntype Write<T, U> = Omit<T, keyof U> & U;\n\ntype WithDevtools<S> = Write<S, StoreDevtools<S>>;\n\ntype NamedSet<T> = WithDevtools<StoreApi<T>>['setState'];\n/*\nChromogen Middleware business logic. Performs 2 main functions:\n    1. Captures initial state for all store properties and writes to ledger as initialRender (For generating initial state tests)\n    2. Wraps set function to capture any subsequent state changes (along with funciton name, arguments, and before/after for changes store slices)\n*/\nconst chromogenImpl: ChromogenImpl = (creatorFunction) => (set, get, api) => {\n  //get initial render and save it to ledger\n  const initialStateEntries = creatorFunction(api.setState, get, api);\n  const initialRender: InitialRender = filterOutFuncs(initialStateEntries);\n  ledger.initialRender = initialRender;\n\n  type S = ReturnType<typeof creatorFunction>;\n  (api.setState as NamedSet<S>) = (partial, replace, action, ...args) => {\n    const oldStore = filterOutFuncs(get());\n    const r = set(partial, replace);\n    const newStore = filterOutFuncs(get());\n    const changedValues = diffStateObjects(oldStore, newStore);\n\n    //create Transaction obj and write it to ledger for generating tests\n    const newAction: Transaction<typeof args> = {\n      action: typeof action === 'string' ? action : 'UnknownAction',\n      changedValues,\n      arguments: args,\n    };\n\n    ledger.transactions.push(newAction);\n    return r;\n  };\n  return creatorFunction(api.setState, get, api);\n};\n\nexport const chromogenZustandMiddleware = chromogenImpl as unknown as Chromogen;\n\n/* Goes through the store object and returns a new object containing state without any actions*/\nconst filterOutFuncs = (store) => {\n  const result = {};\n  for (const [k, v] of Object.entries(store)) {\n    if (typeof v !== 'function') result[k] = v;\n  }\n  return result;\n};\n/* Identifies the difference between initial Store and  newStore containing newly invoked actions */\nconst diffStateObjects = (oldStore, newStore) => {\n  const changedValues = {};\n  for (const [k, v] of Object.entries(newStore)) {\n    if (JSON.stringify(oldStore[k]) !== JSON.stringify(v)) changedValues[k] = v;\n  }\n  return changedValues;\n};\n"
  },
  {
    "path": "package/zustand_generator/src/component/Buttons/RecordingButton.tsx",
    "content": "import React, { useState } from 'react';\nimport RecordButton from './RecordingVariations/Record';\nimport StartButton from './RecordingVariations/Start';\n\nconst RecordingButton = () => {\n  const [isRecording, setIsRecording] = useState(true);\n  const handleClick = () => setIsRecording(!isRecording);\n\n  return (\n    <div\n      style={{\n        display: 'flex',\n        justifyContent: 'center',\n        alignItems: 'center',\n        bottom: '20px',\n        width: '100%',\n      }}\n    >\n      {isRecording ? (\n        <RecordButton handleClick={handleClick} />\n      ) : (\n        <StartButton handleClick={handleClick} />\n      )}\n    </div>\n  );\n};\n\nexport default RecordingButton;\n"
  },
  {
    "path": "package/zustand_generator/src/component/Buttons/RecordingVariations/Record.tsx",
    "content": "import React, { useState } from 'react';\n\nconst Record = (props): JSX.Element => {\n  //hover\n  const [isHover, setIsHover] = useState(false);\n  const handleMouseEnter = () => {\n    setIsHover(true);\n  };\n  const handleMouseLeave = () => {\n    setIsHover(false);\n  };\n\n  //recording\n  const recordButtonShape: React.CSSProperties = {\n    display: 'flex',\n    flexDirection: 'row',\n    alignItems: 'center',\n    position: 'absolute',\n    width: '252px',\n    height: '48px',\n    // left: '1482px',\n    // top: '1081px',\n    borderRadius: '42px',\n    justifyContent: 'center',\n    padding: '14px 24px',\n    columnGap: '16px',\n    background: '#181818',\n    border: '1px solid rgba(243, 246, 248, 0.1)',\n    boxShadow:\n      '0px 18px 24px rgba(0, 0, 0, 0.16), 0px 12px 16px rgba(6, 9, 11, 0.1), 0px 6px 12px rgba(0, 0, 0, 0.18), 0px 1px 20px rgba(0, 0, 0, 0.12)',\n    cursor: 'pointer',\n    bottom: '20px',\n  };\n\n  const recordIcon: React.CSSProperties = {\n    width: '20px',\n    height: '20px',\n    background: '#D75959',\n    opacity: '0.8',\n    boxShadow:\n      '0px 2px 6px rgba(215, 89, 89, 0.24), 0px 6px 10px rgba(215, 89, 89, 0.2), 0px 1px 16px rgba(215, 89, 89, 0.06)',\n    borderRadius: '30px',\n    flex: 'none',\n    order: '0',\n    flexGrow: '0',\n    animation: !isHover ? 'glowing 1500ms infinite' : 'none',\n  };\n\n  const glowingAnimation = `\n  @keyframes glowing {\n    0% { background-color: #D75959; box-shadow: 0 0 3px #D75959; }\n    50% { background-color: #ce4949; box-shadow: 0 0 30px #D75959; }\n    100% { background-color: #D75959; box-shadow: 0 0 3px #D75959; }\n  }\n\n  .glowing {\n    animation: glowing 1500ms infinite;\n  }\n  `;\n\n  const recordText: React.CSSProperties = {\n    fontSize: '14px',\n    lineHeight: '16px',\n    color: '#CB4F4F',\n    opacity: '0.8',\n    flex: 'none',\n    order: '1',\n    flexGrow: '0',\n  };\n\n  //stop\n  const stopButtonShape: React.CSSProperties = {\n    display: 'flex',\n    fontSize: '14px',\n    flexDirection: 'row',\n    alignItems: 'center',\n    position: 'absolute',\n    justifyContent: 'center',\n    width: '252px',\n    height: '48px',\n    // left: '1482px',\n    // top: '1081px',\n    borderRadius: '42px',\n    padding: '14px 24px',\n    columnGap: '16px',\n    background: '#212121',\n    border: '1px solid rgba(243, 246, 248, 0.1)',\n    boxShadow:\n      '0px 18px 24px rgba(0, 0, 0, 0.16), 0px 12px 16px rgba(6, 9, 11, 0.1), 0px 6px 12px rgba(0, 0, 0, 0.18), 0px 1px 20px rgba(0, 0, 0, 0.12)',\n    cursor: 'pointer',\n    bottom: '20px',\n  };\n\n  const stopIcon: React.CSSProperties = {\n    width: '14px',\n    height: '16px',\n    background: 'rgba(243, 246, 248, 0.9)',\n    // opacity: '0.8',\n    // boxShadow: '0px 2px 6px rgba(215, 89, 89, 0.24), 0px 6px 10px rgba(215, 89, 89, 0.2), 0px 1px 16px rgba(215, 89, 89, 0.06)',\n    borderRadius: '1px',\n    flex: 'none',\n    order: '0',\n    flexGrow: '0',\n  };\n\n  const stopText: React.CSSProperties = {\n    height: '16px',\n    fontSize: '14px',\n    lineHeight: '16px',\n    color: '#F3F6F8',\n    opacity: '0.8',\n    flex: 'none',\n    order: '1',\n    flexGrow: '0',\n  };\n\n  return (\n    <>\n      <style>{glowingAnimation}</style>\n      <button\n        style={isHover ? stopButtonShape : recordButtonShape}\n        onMouseEnter={handleMouseEnter}\n        onMouseLeave={handleMouseLeave}\n        onClick={props.handleClick}\n      >\n        <div style={isHover ? stopIcon : recordIcon} />\n\n        <div style={isHover ? stopText : recordText}>\n          {isHover ? 'Stop recording' : 'Recording in progress'}\n        </div>\n      </button>\n    </>\n  );\n};\n\nexport default Record;\n"
  },
  {
    "path": "package/zustand_generator/src/component/Buttons/RecordingVariations/Start.tsx",
    "content": "import React, { useState } from 'react';\n\nconst Start = (props): JSX.Element => {\n  //hover\n  const [isHover, setIsHover] = useState(false);\n  const handleMouseEnter = () => {\n    setIsHover(true);\n  };\n  const handleMouseLeave = () => {\n    setIsHover(false);\n  };\n\n  const startButtonShape: React.CSSProperties = {\n    display: 'flex',\n    width: '252px',\n    flexDirection: 'row',\n    alignItems: 'center',\n    position: 'absolute',\n    justifyContent: 'center',\n    height: '48px',\n    // left: '1482px',\n    // top: '1081px',\n    borderRadius: '42px',\n    padding: '14px 24px',\n    columnGap: '16px',\n    background: '#212121',\n    border: '1px solid rgba(243, 246, 248, 0.1)',\n    cursor: 'pointer',\n    bottom: '20px',\n  };\n\n  const startButtonHover: React.CSSProperties = {\n    display: 'flex',\n    flexDirection: 'row',\n    alignItems: 'center',\n    position: 'absolute',\n    width: '252px',\n    height: '48px',\n    borderRadius: '42px',\n    padding: '14px 24px',\n    columnGap: '16px',\n    justifyContent: 'center',\n    background: '#1C1C1C',\n    border: '1px solid rgba(243, 246, 248, 0.1)',\n    cursor: 'pointer',\n    bottom: '20px',\n  };\n\n  const startIcon: React.CSSProperties = {\n    width: '0',\n    height: '0',\n    borderTop: '8px solid transparent',\n    borderBottom: '8px solid transparent',\n    borderLeft: '16px solid rgba(243, 246, 248, 0.8)',\n    flex: 'none',\n    order: '0',\n    flexGrow: '0',\n    borderRadius: '2px',\n  };\n\n  const startText: React.CSSProperties = {\n    fontSize: '14px',\n    lineHeight: '16px',\n    color: '#F3F6F8',\n    opacity: '0.8',\n    flex: 'none',\n    order: '1',\n    flexGrow: '0',\n  };\n\n  return (\n    <button\n      style={isHover ? startButtonShape : startButtonHover}\n      onClick={props.handleClick}\n      onMouseEnter={handleMouseEnter}\n      onMouseLeave={handleMouseLeave}\n    >\n      <div style={startIcon} />\n      <div style={startText}>Start recording</div>\n    </button>\n  );\n};\n\nexport default Start;\n"
  },
  {
    "path": "package/zustand_generator/src/component/Buttons/SecondaryButton.tsx",
    "content": "import React, { useState } from 'react';\nimport {\n  icon_arrow,\n  icon_copy,\n  icon_download,\n  icon_retract,\n  icon_expand,\n  icon_check,\n} from '../Icons';\n\nconst downloadButton: React.CSSProperties = {\n  font: `-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Roboto\",\"Oxygen\", \"Ubuntu\",\"Cantarell\",\"Fira Sans\",\"Droid Sans\",\"Helvetica Neue\", sans-serif`,\n  color: 'rgba(243, 246, 248, 0.7)',\n  backgroundColor: 'rgb(243, 246, 248, 0.03)',\n  height: '32px',\n  border: '1px solid rgba(243, 246, 248, 0.05)',\n  borderRadius: '6px',\n  display: 'flex',\n  flexDirection: 'row',\n  alignItems: 'center',\n  padding: '6px',\n  gap: '4px',\n};\n\nconst downloadHover: React.CSSProperties = {\n  font: `-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Roboto\",\"Oxygen\", \"Ubuntu\",\"Cantarell\",\"Fira Sans\",\"Droid Sans\",\"Helvetica Neue\", sans-serif`,\n  color: 'rgba(243, 246, 248, 0.7)',\n  backgroundColor: 'rgb(243, 246, 248, 0.1)',\n  height: '32px',\n  border: '1px solid rgba(243, 246, 248, 0.05)',\n  borderRadius: '6px',\n  display: 'flex',\n  flexDirection: 'row',\n  alignItems: 'center',\n  padding: '6px',\n  gap: '4px',\n};\n\nconst downloadTitleContainer: React.CSSProperties = {\n  display: 'flex',\n  alignItems: 'center',\n  gap: '6px',\n};\n\nconst downloadIconBox: React.CSSProperties = {\n  height: '20px',\n  width: '20px',\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n};\n\nconst downloadTitleText: React.CSSProperties = {\n  fontSize: '12px',\n  fontWeight: 500,\n  lineHeight: '16px',\n  color: 'rgba(243, 246, 248, 0.7)',\n  marginRight: '12px',\n};\n\nconst downloadLine: React.CSSProperties = {\n  width: '1px',\n  height: '20px',\n  background: 'rgba(243, 246, 248, 0.05)',\n};\n\nexport const downloadArrow: React.CSSProperties = {\n  width: '20px',\n  height: '20px',\n};\n\ntype Icon = 'download' | 'expand' | 'retract' | 'copy' | 'arrow' | 'check';\n\ninterface Props {\n  icon: Icon;\n  handleClick?: any;\n  value?: string;\n  arrow?: boolean;\n}\n\nconst arrowIcons = (key: Icon): JSX.Element => {\n  if (key == 'download') return icon_download;\n  if (key == 'expand') return icon_expand;\n  if (key == 'retract') return icon_retract;\n  if (key == 'copy') return icon_copy;\n  if (key == 'arrow') return icon_arrow;\n  if (key == 'check') return icon_check;\n  else return icon_arrow;\n};\n\nconst ArrowSection: React.FC = () => {\n  return (\n    <>\n      <div style={downloadLine}></div>\n      <div style={downloadArrow}>{icon_arrow}</div>\n    </>\n  );\n};\n\nconst SecondaryButton: React.FC<Props> = ({ value, arrow, icon, handleClick }) => {\n  const [isHover, setIsHover] = useState(false);\n  const handleMouseEnter = () => {\n    setIsHover(true);\n  };\n  const handleMouseLeave = () => {\n    setIsHover(false);\n  };\n\n  return (\n    <button\n      type=\"button\"\n      style={isHover ? downloadHover : downloadButton}\n      onClick={handleClick}\n      onMouseEnter={handleMouseEnter}\n      onMouseLeave={handleMouseLeave}\n    >\n      <div style={downloadTitleContainer}>\n        <div style={downloadIconBox}>{arrowIcons(icon)}</div>\n        {value && <div style={downloadTitleText}>{value}</div>}\n      </div>\n      {arrow && <ArrowSection />}\n    </button>\n  );\n};\n\nexport default SecondaryButton;\n"
  },
  {
    "path": "package/zustand_generator/src/component/ChromogenZustandObserver.tsx",
    "content": "import Editor from './Editor';\nimport EditorTab from './EditorTab';\nimport React, { useState } from 'react';\nimport { generateTests } from './component-utils';\nimport GlobalStyle from '../GlobalStyle';\n\nconst panel: React.CSSProperties = {\n  display: 'flex',\n  position: 'relative',\n  // width: '531.49px'\n};\n\ninterface Props {\n  children: JSX.Element;\n}\n\nexport const ChromogenZustandObserver: React.FC<Props> = ({ children }): JSX.Element => {\n  const [code, setCode] = React.useState('');\n  const [storeMap] = React.useState<Map<string, string>>(new Map());\n  const [isHidden, setIsHidden] = useState(false);\n\n  const timer = setInterval(() => {\n    console.log('Firing');\n    setCode(String(generateTests(storeMap)));\n  }, 1000);\n\n  React.useEffect(() => {\n    console.log(code);\n    timer;\n  }, [timer]);\n\n  // React.useEffect(() => console.log(ledger.transactions[2].changedValues), []);\n\n  return (\n    <div style={panel}>\n      {children}\n      {isHidden ? (\n        <EditorTab setIsHidden={setIsHidden} isHidden={isHidden} />\n      ) : (\n        <Editor code={code} setIsHidden={setIsHidden} isHidden={isHidden} />\n      )}\n      <GlobalStyle />\n    </div>\n  );\n};\n"
  },
  {
    "path": "package/zustand_generator/src/component/Editor.tsx",
    "content": "import React, { useState } from 'react';\nimport CodeEditor from '@uiw/react-textarea-code-editor';\nimport NumberList from './Numbers';\nimport { Header } from './Header';\nimport RecordingButton from './Buttons/RecordingButton';\n\nconst editorStyle: React.CSSProperties = {\n  display: 'flex',\n  flexDirection: 'column',\n  height: '100%',\n  overflow: 'auto',\n  borderLeft: '1px solid rgba(243,246,248,.1)',\n  backgroundColor: '#1C1C1C',\n  width: '50vw',\n};\nconst codePanel: React.CSSProperties = {\n  display: 'flex',\n  // flexGrow: 1,\n  overflowY: 'scroll',\n  height: 'calc(100vh - 56px)',\n};\n\ninterface Props {\n  code: string;\n  setIsHidden: Function;\n  isHidden: boolean;\n}\nconst Editorfield = ({ code, isHidden, setIsHidden }: Props): JSX.Element => {\n  const [, setInnerCode] = useState(code);\n  let breakLine = 0;\n\n  for (let curr = 0; curr < code.length; curr++) {\n    if (code[curr] == '\\n') breakLine++;\n  }\n\n  console.log(breakLine);\n\n  return (\n    <div style={editorStyle}>\n      <Header isHidden={isHidden} setIsHidden={setIsHidden} />\n      <div style={codePanel}>\n        <NumberList number={breakLine ? breakLine + 10 : 0} />\n        <CodeEditor\n          data-color-mode=\"dark\"\n          value={code}\n          language=\"js\"\n          placeholder=\"\"\n          onChange={(evn) => setInnerCode(evn.target.value)}\n          padding={15}\n          style={{\n            maxWidth: 1000,\n            width: 'calc(100% - 60px)',\n            maxHeight: '100vh',\n            overflow: 'visible',\n            fontSize: 12,\n            backgroundColor: '#1c1c1c',\n            fontFamily:\n              'ui-monospace, SFMono-Regular, SF Mono, Consolas, Liberation Mono, Menlo,monospace',\n          }}\n        />\n      </div>\n      <RecordingButton />\n    </div>\n  );\n};\n\nexport default Editorfield;\n"
  },
  {
    "path": "package/zustand_generator/src/component/EditorTab.tsx",
    "content": "import React from 'react';\nimport SecondaryButton from './Buttons/SecondaryButton';\n\ntype Props = {\n  setIsHidden: Function;\n  isHidden: boolean;\n};\n\nconst EditorTab = (props: Props): JSX.Element => {\n  const { isHidden, setIsHidden } = props;\n  return (\n    <div style={{ position: 'absolute', top: '8px', right: '16px' }}>\n      <SecondaryButton value=\"Chromogen\" icon=\"expand\" handleClick={() => setIsHidden(!isHidden)} />\n    </div>\n  );\n};\n\nexport default EditorTab;\n"
  },
  {
    "path": "package/zustand_generator/src/component/Header.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport SecondaryButton from './Buttons/SecondaryButton';\nimport { generateFile, generateTests } from './component-utils';\n\nconst toolBar: React.CSSProperties = {\n  display: 'flex',\n  height: '56px',\n  width: '100%',\n  padding: '8px 16px',\n  alignItems: 'center',\n  gap: '8px',\n  borderBottom: `1px solid rgba(243,246,248,.05)`,\n};\n\nconst toolBarLogoBox: React.CSSProperties = {\n  height: '40px',\n  width: '40px',\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n};\n\nconst toolBarTitleContainer: React.CSSProperties = {\n  display: 'flex',\n  flexDirection: 'column',\n  color: '#fff',\n  lineHeight: '16px',\n  opacity: 0.9,\n  marginLeft: '8px',\n  rowGap: '4px',\n  flexGrow: 1,\n};\n\nconst toolBarTitle: React.CSSProperties = {\n  fontSize: '16px',\n  fontWeight: 500,\n  fontFamily: 'Inter !important',\n};\n\nconst toolBarDescription: React.CSSProperties = {\n  fontSize: '12px',\n  fontWeight: 400,\n};\n\nexport const copyButton: React.CSSProperties = {\n  height: '32px',\n  width: '32px',\n  backgroundColor: 'rgb(243, 246, 248, 0.03)',\n  border: '1px solid rgba(243, 246, 248, 0.05)',\n  borderRadius: '6px',\n  display: 'flex',\n  alignItems: 'center',\n  padding: '6px',\n};\n\nexport const minimizeButton: React.CSSProperties = {\n  height: '24px',\n  width: '24px',\n  backgroundColor: 'white',\n  marginLeft: '10px',\n  padding: 0,\n  cursor: 'pointer',\n};\n\nexport const minimizeIcon: React.CSSProperties = {};\n\nexport const Header = ({ isHidden, setIsHidden }) => {\n  const [file, setFile] = useState<undefined | string>(undefined);\n  const [copied, setCopied] = useState(false);\n\n  useEffect(() => {\n    copyOff();\n  }, [copied]);\n\n  function copyOff() {\n    setTimeout(() => {\n      setCopied(false);\n    }, 3000);\n  }\n\n  return (\n    <div style={toolBar}>\n      <div style={toolBarLogoBox}>\n        <div>\n          <svg\n            width=\"40\"\n            height=\"40\"\n            viewBox=\"0 0 40 40\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <mask id=\"mask0_40_871\" maskUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"40\" height=\"40\">\n              <rect width=\"40\" height=\"40\" fill=\"#D9D9D9\" />\n            </mask>\n            <g mask=\"url(#mask0_40_871)\">\n              <path\n                d=\"M20 35C18.1947 35 16.514 34.5627 14.958 33.688C13.4027 32.8127 12.236 31.5833 11.458 30H6.667V27.208H10.375C10.2363 26.5413 10.16 25.8747 10.146 25.208C10.132 24.5413 10.125 23.861 10.125 23.167H6.625V20.375H10.125C10.125 19.653 10.132 18.9377 10.146 18.229C10.16 17.521 10.264 16.8197 10.458 16.125H6.667V13.333H11.542C11.9307 12.611 12.4237 11.9583 13.021 11.375C13.6183 10.7917 14.278 10.3057 15 9.917L11.958 6.875L13.875 5L17.667 8.792C18.4443 8.542 19.229 8.417 20.021 8.417C20.8123 8.417 21.597 8.542 22.375 8.792L26.208 5L28.125 6.875L25.083 9.917C25.8057 10.3057 26.4517 10.7917 27.021 11.375C27.5903 11.9583 28.0973 12.611 28.542 13.333H33.375V16.125H29.542C29.736 16.8197 29.833 17.521 29.833 18.229V20.375H33.375V23.167H29.833V25.208C29.833 25.8747 29.7637 26.5413 29.625 27.208H33.375V30H28.583C27.8057 31.6113 26.639 32.8473 25.083 33.708C23.5277 34.5693 21.8333 35 20 35ZM20 32.208C21.9447 32.208 23.6043 31.5277 24.979 30.167C26.3543 28.8057 27.042 27.1527 27.042 25.208V18.333C27.042 16.389 26.3543 14.7293 24.979 13.354C23.6043 11.9793 21.9447 11.292 20 11.292C18.0553 11.292 16.3957 11.9793 15.021 13.354C13.6457 14.7293 12.958 16.389 12.958 18.333V25.208C12.958 27.1527 13.6457 28.8057 15.021 30.167C16.3957 31.5277 18.0553 32.208 20 32.208ZM16.667 26.667H23.333V23.875H16.667V26.667ZM16.667 19.625H23.333V16.875H16.667V19.625Z\"\n                fill=\"#fff\"\n                fill-opacity=\"0.85\"\n              />\n            </g>\n          </svg>\n        </div>\n      </div>\n      <div style={toolBarTitleContainer}>\n        <div style={toolBarTitle}>Chromogen Tests</div>\n        <div style={toolBarDescription}>Interact with the app to generate tests</div>\n      </div>\n      <a\n        style={{ textDecoration: 'none' }}\n        download=\"chromogen.test.js\"\n        href={file}\n        id=\"chromogen-download\"\n      >\n        <SecondaryButton\n          arrow\n          value={'Download'}\n          icon=\"download\"\n          handleClick={() => generateFile(setFile, new Map())}\n        />\n      </a>\n      <SecondaryButton\n        icon={copied ? 'check' : 'copy'}\n        handleClick={() => {\n          navigator.clipboard.writeText(generateTests(new Map())[0]);\n          setCopied(true);\n        }}\n      />\n      <SecondaryButton icon=\"retract\" handleClick={() => setIsHidden(!isHidden)} />\n    </div>\n  );\n};\n"
  },
  {
    "path": "package/zustand_generator/src/component/Icons.tsx",
    "content": "import React from 'react';\n\nexport const icon_download = (\n  <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <mask id=\"mask0_40_887\" maskUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"20\" height=\"20\">\n      <rect width=\"20\" height=\"20\" fill=\"#D9D9D9\" />\n    </mask>\n    <g mask=\"url(#mask0_40_887)\">\n      <path\n        d=\"M10 14L13 11L11.938 9.938L10.75 11.125V8H9.25V11.125L8.062 9.938L7 11L10 14ZM5.5 18C5.08333 18 4.72933 17.854 4.438 17.562C4.146 17.2707 4 16.9167 4 16.5V6L8 2H14.5C14.9167 2 15.2707 2.146 15.562 2.438C15.854 2.72933 16 3.08333 16 3.5V16.5C16 16.9167 15.854 17.2707 15.562 17.562C15.2707 17.854 14.9167 18 14.5 18H5.5ZM5.5 16.5H14.5V3.5H8.625L5.5 6.625V16.5Z\"\n        fill=\"#F3F6F8\"\n        fill-opacity=\"0.6\"\n      />\n    </g>\n  </svg>\n);\n\nexport const icon_copy = (\n  <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <mask id=\"mask0_40_878\" maskUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"20\" height=\"20\">\n      <rect width=\"20\" height=\"20\" fill=\"#D9D9D9\" />\n    </mask>\n    <g mask=\"url(#mask0_40_878)\">\n      <path\n        d=\"M4.5 18C4.08333 18 3.72933 17.854 3.438 17.562C3.146 17.2707 3 16.9167 3 16.5V5H4.5V16.5H14V18H4.5ZM7.5 15C7.08333 15 6.72933 14.854 6.438 14.562C6.146 14.2707 6 13.9167 6 13.5V3.5C6 3.08333 6.146 2.72933 6.438 2.438C6.72933 2.146 7.08333 2 7.5 2H15.5C15.9167 2 16.2707 2.146 16.562 2.438C16.854 2.72933 17 3.08333 17 3.5V13.5C17 13.9167 16.854 14.2707 16.562 14.562C16.2707 14.854 15.9167 15 15.5 15H7.5ZM7.5 13.5H15.5V3.5H7.5V13.5Z\"\n        fill=\"#F3F6F8\"\n        fill-opacity=\"0.6\"\n      />\n    </g>\n  </svg>\n);\n\nexport const icon_retract = (\n  <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <mask id=\"mask0_40_882\" maskUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"24\" height=\"24\">\n      <rect width=\"24\" height=\"24\" transform=\"matrix(-1 0 0 1 24 0)\" fill=\"#D9D9D9\" />\n    </mask>\n    <g mask=\"url(#mask0_40_882)\">\n      <path\n        d=\"M13 18L19 12L13 6L11.6 7.4L16.175 12L11.6 16.6L13 18ZM6.4 18L12.4 12L6.4 6L5 7.4L9.575 12L5 16.6L6.4 18Z\"\n        fill=\"#F3F6F8\"\n        fill-opacity=\"0.6\"\n      />\n    </g>\n  </svg>\n);\n\nexport const icon_expand = (\n  <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <mask id=\"mask0_40_765\" maskUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"24\" height=\"24\">\n      <rect width=\"24\" height=\"24\" transform=\"matrix(1 0 0 -1 0 24)\" fill=\"#D9D9D9\" />\n    </mask>\n    <g mask=\"url(#mask0_40_765)\">\n      <path\n        d=\"M11 6L5 12L11 18L12.4 16.6L7.825 12L12.4 7.4L11 6ZM17.6 6L11.6 12L17.6 18L19 16.6L14.425 12L19 7.4L17.6 6Z\"\n        fill=\"#F3F6F8\"\n        fill-opacity=\"0.4\"\n      />\n    </g>\n  </svg>\n);\n\nexport const icon_arrow = (\n  <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <mask id=\"mask0_40_892\" maskUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"20\" height=\"20\">\n      <rect width=\"20\" height=\"20\" fill=\"#D9D9D9\" />\n    </mask>\n    <g mask=\"url(#mask0_40_892)\">\n      <path d=\"M10 12L6 8H14L10 12Z\" fill=\"#F3F6F8\" fill-opacity=\"0.4\" />\n    </g>\n  </svg>\n);\n\nexport const icon_check = (\n  <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <mask id=\"mask0_148_58\" maskUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"24\" height=\"24\">\n      <rect width=\"24\" height=\"24\" fill=\"#D9D9D9\" />\n    </mask>\n    <g mask=\"url(#mask0_148_58)\">\n      <path\n        d=\"M9.55015 18.4513L3.39795 12.2991L5.28927 10.4078L9.55015 14.6687L18.711 5.50781L20.6024 7.39914L9.55015 18.4513Z\"\n        fill=\"#F3F6F8\"\n        fill-opacity=\"0.6\"\n      />\n    </g>\n  </svg>\n);\n"
  },
  {
    "path": "package/zustand_generator/src/component/Numbers.tsx",
    "content": "import React from 'react';\n\nconst listStyle: React.CSSProperties = {\n  paddingBlock: '28px',\n  display: 'flex',\n  flexDirection: 'column',\n  alignContent: 'flex-end',\n  textAlign: 'end',\n  background: '#1c1c1c',\n  paddingInline: '16px',\n  border: '1px solid #1c1c1c',\n  height: 'auto',\n  width: '60px',\n};\n\nconst numberStyle: React.CSSProperties = {\n  fontSize: 12,\n  color: '#747478',\n  fontFamily: 'ui-monospace, SFMono-Regular, SF Mono, Consolas, Liberation Mono, Menlo,monospace',\n};\n\nconst unique = (val: number | string) => (\n  <p key={'number' + val} style={numberStyle}>\n    {val}\n  </p>\n);\n\nconst numerous = (num = 1000) => {\n  let pointer: number = 1;\n  let allNumbers: JSX.Element[] = [];\n\n  while (pointer < num) {\n    allNumbers = [...allNumbers, unique(pointer)];\n    pointer++;\n  }\n\n  console.log(allNumbers);\n\n  return allNumbers;\n};\n\nconst NumberList = ({ number }): JSX.Element => <div style={listStyle}>{numerous(number)}</div>;\n\nexport default NumberList;\n"
  },
  {
    "path": "package/zustand_generator/src/component/Resizing/Resizer.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\n\nconst ResizerStyle = styled.div`\n  position: absolute;\n  cursor: ew-resize;\n  width: 2px;\n  height: 100%;\n  z-index: 1;\n  left: -1;\n  top: 0;\n  &:hover {\n    background: #4848be;\n  }\n`;\n\ninterface ResizerProps {\n  onResize: Function;\n}\n\nconst Resizer: React.FC<ResizerProps> = ({ onResize }) => {\n  const [direction, setDirection] = useState('');\n  const [mouseDown, setMouseDown] = useState(false);\n\n  useEffect(() => {\n    const handleMouseMove = (e) => {\n      if (!direction) return;\n      onResize(direction, e.movementX, e.movementY);\n    };\n\n    if (mouseDown) {\n      window.addEventListener('mousemove', handleMouseMove);\n    }\n\n    return () => window.removeEventListener('mousemove', handleMouseMove);\n  }, [mouseDown, direction, onResize]);\n\n  useEffect(() => {\n    const handleMouseUp = () => setMouseDown(false);\n    window.addEventListener('mouseup', handleMouseUp);\n\n    return () => window.removeEventListener('mouseup', handleMouseUp);\n  }, []);\n\n  const handleMouseDown = (direction) => {\n    setDirection(direction);\n    setMouseDown(true);\n  };\n\n  return <ResizerStyle className=\"left\" onMouseDown={() => handleMouseDown('left')}></ResizerStyle>;\n};\n\nexport default Resizer;\n"
  },
  {
    "path": "package/zustand_generator/src/component/component-utils.ts",
    "content": "/* eslint-disable */\nimport type { CSSProperties } from 'react';\n// import type { SerializableParam } from 'recoil';\nimport type { Ledger } from '../types';\n\nimport { ledger } from '../utils/ledger';\nimport { output } from '../output/output';\n/* eslint-enable */\n\nconst buttonStyle: CSSProperties = {\n  display: 'inline-block',\n  margin: '8px',\n  marginLeft: '13px',\n  padding: '0px',\n  height: '25px',\n  width: '65px',\n  borderRadius: '4px',\n  justifyContent: 'space-evenly',\n  border: '1px',\n  cursor: 'pointer',\n  color: '#90d1f0',\n  fontSize: '10px',\n};\n\nconst divStyle: CSSProperties = {\n  display: 'flex',\n  position: 'relative',\n  height: '100%',\n  top: '0px',\n  right: '0px',\n  bottom: '0px',\n  width: '30vw',\n  backgroundColor: '#222222',\n  borderRadius: '4px',\n  margin: 0,\n  padding: 0,\n  zIndex: 999999,\n};\n\nconst playStyle: CSSProperties = {\n  boxSizing: 'border-box',\n  marginLeft: '25px',\n  borderStyle: 'solid',\n  borderWidth: '7px 0px 7px 14px',\n};\n\nconst pauseStyle: CSSProperties = {\n  width: '14px',\n  height: '14px',\n  borderWidth: '0px 0px 0px 10px',\n  borderStyle: 'double',\n  marginLeft: '27px',\n};\n\nexport const styles = { buttonStyle, divStyle, playStyle, pauseStyle };\n\n/*\n generateFile generates test file & sets download URL\n The passed in setFile function updates _file_ state in Chromogen observer \n Applying only at point-of-download keeps performance cost low for users who\n don't need to pass nodes while creating a moderate performance hit for others\n only while downloading, never while interacting with their app.\n*/\nexport const generateFile = (setFile: Function, storeMap: Map<string, string>): string[] => {\n  const tests = generateTests(storeMap);\n  const blob = new Blob(tests);\n  setFile(URL.createObjectURL(blob));\n  return tests;\n};\n\n/* generateTests is invoked within generateFile, returning our desired test string within an array */\nexport const generateTests = (storeMap: Map<string, string>): string[] => {\n  const { initialRender, transactions } = ledger;\n\n  const finalLedger: Ledger =\n    storeMap.size > 0\n      ? {\n          initialRender,\n          transactions: transactions,\n        }\n      : { ...ledger };\n\n  return [output(finalLedger)];\n};\n"
  },
  {
    "path": "package/zustand_generator/src/component/panel.tsx",
    "content": "/* eslint-disable */\nimport React, { useState, useEffect } from 'react';\nimport { useStore } from '../utils/store';\nimport { styles, generateFile, generateTests } from './component-utils';\n/* eslint-enable */\n\n/* using a zustand store to keep track of recording state */\nconst selector = (state) => ({\n  recording: state.recording,\n  toggleRecording: state.toggleRecording,\n});\n\nexport const Panel: React.FC = () => {\n  // Initializing as undefined over null to match React typing for AnchorHTML attributes\n  const [file, setFile] = useState<undefined | string>(undefined);\n  const [storeMap] = useState<Map<string, string>>(new Map());\n  const { recording, toggleRecording } = useStore<{\n    recording: boolean;\n    toggleRecording: Function;\n  }>(selector);\n\n  // Auto-click download link when a new file is generated (via button click)\n  useEffect(() => document.getElementById('chromogen-download')!.click(), [file]);\n  // ! to get around strict null check in tsconfig\n\n  const [pauseColor, setPauseColor] = useState('#90d1f0');\n  const pauseBorderStyle = {\n    borderColor: `${pauseColor}`,\n  };\n\n  const [playColor, setPlayColor] = useState('transparent transparent transparent #90d1f0');\n  const playBorderStyle = {\n    borderColor: `${playColor}`,\n  };\n\n  return (\n    <>\n      {\n        <div>\n          <div style={styles.divStyle}>\n            <h1>Testing!</h1>\n            <button\n              aria-label={recording ? 'pause' : 'record'}\n              id=\"chromogen-toggle-record\"\n              style={{ ...styles.buttonStyle, backgroundColor: '#7f7f7f' }}\n              type=\"button\"\n              onClick={() => {\n                toggleRecording();\n                // if (!recording) return true;\n                // return false;\n              }}\n              onMouseEnter={() =>\n                recording\n                  ? setPauseColor('#f6f071')\n                  : setPlayColor('transparent transparent transparent #f6f071')\n              }\n              onMouseLeave={() =>\n                recording\n                  ? setPauseColor('#90d1f0')\n                  : setPlayColor('transparent transparent transparent #90d1f0')\n              }\n            >\n              <a>\n                {recording ? (\n                  <div style={{ ...styles.pauseStyle, ...pauseBorderStyle }}></div>\n                ) : (\n                  <div style={{ ...styles.playStyle, ...playBorderStyle }}></div>\n                )}\n              </a>\n            </button>\n            <button\n              aria-label=\"capture test\"\n              id=\"chromogen-generate-file\"\n              style={{\n                ...styles.buttonStyle,\n                backgroundColor: '#7f7f7f',\n                marginLeft: '-2px',\n                marginRight: '13px',\n              }}\n              type=\"button\"\n              onClick={() => generateFile(setFile, storeMap)}\n              onMouseEnter={() =>\n                (document.getElementById('chromogen-generate-file')!.style.color = '#f6f071')\n              }\n              onMouseLeave={() =>\n                (document.getElementById('chromogen-generate-file')!.style.color = '#90d1f0')\n              }\n            >\n              <a>{'Download'}</a>\n            </button>\n            <button\n              aria-label=\"copy test\"\n              id=\"chromogen-copy-test\"\n              style={{\n                ...styles.buttonStyle,\n                backgroundColor: '#7f7f7f',\n                marginLeft: '-2px',\n                marginRight: '13px',\n              }}\n              type=\"button\"\n              onClick={() => {\n                navigator.clipboard.writeText(generateTests(storeMap)[0]);\n              }}\n              onMouseEnter={() =>\n                (document.getElementById('chromogen-copy-test')!.style.color = '#f6f071')\n              }\n              onMouseLeave={() =>\n                (document.getElementById('chromogen-copy-test')!.style.color = '#90d1f0')\n              }\n            >\n              <a>{'Copy To Clipboard'}</a>\n            </button>\n          </div>\n        </div>\n      }\n      <a\n        download=\"chromogen.test.js\"\n        href={file}\n        id=\"chromogen-download\"\n        style={{ display: 'none' }}\n      >\n        Download Test\n      </a>\n    </>\n  );\n};\n"
  },
  {
    "path": "package/zustand_generator/src/output/output-utils.ts",
    "content": "/* eslint-disable */\nimport type { Transaction, InitialRender } from '../types';\n// } from '../types';\n\nimport { dummyParam } from '../utils/utils';\n\n/* eslint-enable */\n\n/* ----- HELPER FUNCTIONS ----- */\n\nexport function importZustandStore(): string {\n  return `import useStore from '<ADD STORE FILEPATH>';`;\n}\n\nexport function testInitialState(initialRender: InitialRender): string {\n  return Object.entries(initialRender).reduce((acc, [k, v]) => {\n    return (\n      acc\n      + `\\tit('${k} should initialize correctly', () => {\\n\\t\\texpect(result.current.${k}).toStrictEqual(${JSON.stringify(\n        v,\n      )});\\n\\t});\\n\\n`\n    );\n  }, '');\n}\n\nconst dummyTransaction = { action: dummyParam, changedValues: {} };\n\n//Takes in an array of transactions and returns a full set of tests (\"it blocks\") for all actions and corresponding state changes\nexport function testStateChangesAct(transactions: Transaction<any>[]): string {\n  //Groups transactions together based on whether the transactions impact the same slice of state\n  //Each \"group\" of transactions will affect each store parameter 0 or 1 times.\n  let groupedTransactions: Transaction<any>[][] = [...transactions, dummyTransaction].reduce(\n    (\n      acc: {\n        groups: Transaction<any>[][];\n        currentGroup: Transaction<any>[];\n        changedValues: { [nameOfChangedValue: string]: any };\n      },\n      cur,\n    ) => {\n      if (\n        Object.keys(cur.changedValues).some((v) => acc.changedValues[v])\n        || cur.action === dummyParam\n      ) {\n        acc.groups.push(acc.currentGroup);\n        acc.currentGroup = [cur];\n        acc.changedValues = Object.keys(cur.changedValues).reduce((acc, k) => {\n          acc[k] = true;\n          return acc;\n        }, {});\n      } else {\n        acc.currentGroup.push(cur);\n        Object.keys(cur.changedValues).forEach((k) => (acc.changedValues[k] = true));\n      }\n      return acc;\n    },\n    { groups: [], currentGroup: [], changedValues: {} },\n  ).groups;\n\n  //For each group of transactions, we generate an \"It block\"\n  return groupedTransactions.reduce(\n    (acc, group) => {\n      const { str, actBlock } = generateItBlock(group);\n      acc.str += str;\n      acc.actStatements = actBlock;\n      return acc;\n    },\n    { str: '', actStatements: '' },\n  ).str;\n}\n//Takes in an entry for a slice of state and generates an expect statement asserting that the state properties have correct value in the store\nexport function testStateChangesExpect([propertyName, newValue]: [string, any]): string {\n  return `\\nexpect(result.current.${propertyName}).toStrictEqual(${JSON.stringify(newValue)});`;\n}\n\n//Takes in a transaction and generates an act statement using the action name and argument(s)\nexport function generateActLine<T extends any[]>(t: Transaction<T>): string {\n  const { action } = t;\n  const args = t.arguments;\n  return `\\tresult.current.${action}(${args?.map((arg) => JSON.stringify(arg)).join(', ')});\\n`;\n}\n\n//Takes in an array of grouped Transactions and returns an It Block (unit test) with all act &\n// expect statements for transactions in input\nfunction generateItBlock(transactions: Transaction<any>[]): { str: string; actBlock: string } {\n  const valuesChanged: string[] = [];\n  let expectBlock = '';\n\n  transactions.forEach((t) =>\n    Object.entries(t.changedValues).forEach(([changedValue, newValue]) => {\n      valuesChanged.push(changedValue);\n      expectBlock += testStateChangesExpect([changedValue, newValue]);\n    }),\n  );\n\n  let newActBlock = transactions.map(generateActLine).join('');\n\n  return {\n    str: `\\n\\tit('${valuesChanged.join(' & ')} should update correctly', () => {\n      const { result } = renderHook(useStore);\n  \n      act(() => {\\n${newActBlock}\\n});\n  \n      ${expectBlock}  \n    });`,\n    actBlock: newActBlock,\n  };\n}\n"
  },
  {
    "path": "package/zustand_generator/src/output/output.ts",
    "content": "/* eslint-disable */\nimport type { Ledger } from '../types';\nimport { importZustandStore, testInitialState, testStateChangesAct } from './output-utils';\n/* eslint-enable */\n\n/* ----- MAIN ----- */\n/* Output takes in initialRender and transactions from the ledger and tests them from the functions in output-utils*/\nexport const output = ({ initialRender, transactions }: Ledger): string =>\n  `\nimport { renderHook, act } from '@testing-library/react';\n${importZustandStore()}\n\ndescribe('INITIAL RENDER', () => { \n  const { result } = renderHook(useStore); \n  \n  ${testInitialState(initialRender)}\n});\n\n\ndescribe('STATE CHANGES', () => { \n  const { result } = renderHook(useStore);\n \n  ${testStateChangesAct(transactions)}\n});`;\n\nexport const unitOutput = (initialRender: any, action: any): string => {\n  console.log('within unitOutput. init, action : ', initialRender, action);\n  let retString = '';\n  if (initialRender) {\n    console.log('within unitOutput initialRender');\n    retString += `\n    import { renderHook, act } from '@testing-library/react';\n    ${importZustandStore()}\n    describe('INITIAL RENDER', () => {\n      const { result } = renderHook(useStore);\n      ${testInitialState(initialRender)}\n    });\n    `;\n  } else if (action) {\n    console.log('within unitOutput action');\n    retString += `\n    describe('STATE CHANGES', () => {\n      const { result } = renderHook(useStore);\n      ${testStateChangesAct([action])}\n    });`;\n  }\n  return retString;\n};\n\n//NOTE: Test output is not linted/formatted in any meaningful way. The Chromogen team recommends formatting tests in line with your personal or organizational preferences;\n"
  },
  {
    "path": "package/zustand_generator/src/types.ts",
    "content": "// ----- EXPORTING TYPES TO BE USED IN SRC/.TSX FILES -----\n\ntype NotAFunction = { [k: string]: unknown } & ({ bind?: never } | { call?: never });\n\nexport type InitialRender = {\n  [stateParam: string]: NotAFunction;\n};\n\nexport interface Transaction<T extends any[]> {\n  action: string;\n  arguments?: T;\n  changedValues: {\n    [nameOfChangedValue: string]: NotAFunction;\n  };\n}\n\nexport interface Ledger {\n  initialRender: InitialRender;\n  transactions: Transaction<any>[];\n}\n"
  },
  {
    "path": "package/zustand_generator/src/utils/ledger.ts",
    "content": "import type { Ledger } from '../types';\n\nexport const ledger: Ledger = {\n  initialRender: {},\n  transactions: [],\n};\n"
  },
  {
    "path": "package/zustand_generator/src/utils/store.ts",
    "content": "import create from 'zustand';\n\ninterface RecordingState {\n  recording: boolean;\n  toggleRecording: () => void;\n}\n\n/*\nAllows for recording to always be on during page load \nand the ability to pause recording\n*/\nexport const useStore = create<RecordingState>((set) => ({\n  recording: true,\n  toggleRecording: () => {\n    set((state) => ({ recording: !state.recording }), false);\n  },\n}));\n"
  },
  {
    "path": "package/zustand_generator/src/utils/utils.ts",
    "content": "export const dummyParam = 'chromogenDummyParam';\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"chromogen-root\",\n  \"version\": \"5.0.1\",\n  \"description\": \"simple, interaction-driven test generator for Recoil and Zustand apps\",\n  \"scripts\": {\n    \"ci-all\": \"(npm ci); (cd ./package && npm ci); (cd ./dev-tool && npm ci); (cd ./demo-todo && npm ci);\",\n    \"build\": \"echo \\\"ERROR! Nothing to build in root directory.\\n\\\"\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/oslabs-beta/Chromogen.git\"\n  },\n  \"contributors\": [\n    {\n      \"name\": \"Brach Burdick\",\n      \"url\": \"https://github.com/sirbrachthepale/\"\n    },\n    {\n      \"name\": \"Francois Denavaut\",\n      \"url\": \"https://github.com/dnvt/\"\n    },\n    {\n      \"name\": \"Maggie Kwan\",\n      \"url\": \"https://github.com/maggiekwan/\"\n    },\n    {\n      \"name\": \"Lawrence Liang\",\n      \"url\": \"https://github.com/Lawliang/\"\n    },\n    {\n      \"name\": \"Michelle Holland\",\n      \"url\": \"https://github.com/michellebholland/\"\n    },\n    {\n      \"name\": \"Jim Chen\",\n      \"url\": \"https://github.com/chenchingk\"\n    },\n    {\n      \"name\": \"Andy Wang\",\n      \"url\": \"https://github.com/andywang23\"\n    },\n    {\n      \"name\": \"Connor Rose Delisle\",\n      \"url\": \"https://github.com/connorrose\"\n    },\n    {\n      \"name\": \"Amy Yee\",\n      \"url\": \"https://github.com/amyy98\"\n    },\n    {\n      \"name\": \"Cameron Greer\",\n      \"url\": \"https://github.com/cgreer011\"\n    },\n    {\n      \"name\": \"Jinseon Shin\",\n      \"url\": \"https://github.com/wlstjs\"\n    },\n    {\n      \"name\": \"Nicholas Shay\",\n      \"url\": \"https://github.com/nicholasjs\"\n    },\n    {\n      \"name\": \"Ryan Tumel\",\n      \"url\": \"https://github.com/rtumel123\"\n    },\n    {\n      \"name\": \"Marcellies Pettiford\",\n      \"url\": \"https://github.com/mp-04\"\n    },\n    {\n      \"name\": \"Sung Kim\",\n      \"url\": \"https://github.com/smk53664\"\n    },\n    {\n      \"name\": \"Lina Lee\",\n      \"url\": \"https://github.com/lina4lee\"\n    },\n    {\n      \"name\": \"Erica Oh\",\n      \"url\": \"https://github.com/ericaysoh\"\n    },\n    {\n      \"name\": \"Dani Almaraz\",\n      \"url\": \"https://github.com/dtalmaraz\"\n    },\n    {\n      \"name\": \"Craig Boswell\",\n      \"url\": \"https://github.com/crgb0s\"\n    },\n    {\n      \"name\": \"Hussein Ahmed\",\n      \"url\": \"https://github.com/Hali3030\"\n    },\n    {\n      \"name\": \"Ian Kila\",\n      \"url\": \"https://github.com/iannkila\"\n    },\n    {\n      \"name\": \"Yuehao Wong\",\n      \"url\": \"https://github.com/yuehaowong\"\n    }\n  ],\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/oslabs-beta/Chromogen/issues\"\n  },\n  \"homepage\": \"https://github.com/oslabs-beta/Chromogen#readme\",\n  \"dependencies\": {\n    \"@uiw/react-textarea-code-editor\": \"^2.1.1\",\n    \"dependency-cruiser\": \"^12.9.0\",\n    \"react\": \"^18.0.0\",\n    \"react-dom\": \"^18.0.0\",\n    \"recoil\": \"^0.7.2\",\n    \"redux\": \"^4.0.5\",\n    \"styled-components\": \"^5.3.6\",\n    \"zustand\": \"^4.1.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.11.6\",\n    \"@babel/preset-env\": \"^7.11.5\",\n    \"@babel/preset-react\": \"^7.10.4\",\n    \"@babel/preset-typescript\": \"^7.10.4\",\n    \"@testing-library/react\": \"^13.1.1\",\n    \"@types/node\": \"^14.11.2\",\n    \"@types/react\": \"^18.0.6\",\n    \"@types/react-dom\": \"^18.0.2\",\n    \"@types/styled-components\": \"^5.1.26\",\n    \"babel-jest\": \"^26.3.0\",\n    \"coveralls\": \"^3.1.0\",\n    \"css-loader\": \"^6.7.3\",\n    \"eslint-config-airbnb-typescript\": \"^17.0.0\",\n    \"jest\": \"^26.4.2\",\n    \"react-test-renderer\": \"^18.0.0\",\n    \"typescript\": \"^4.0.3\"\n  }\n}\n"
  }
]