[
  {
    "path": ".eslintrc.cjs",
    "content": "module.exports = {\n  extends: [\"eslint:recommended\", \"plugin:@typescript-eslint/recommended\"],\n  parser: \"@typescript-eslint/parser\",\n  plugins: [\"@typescript-eslint\"],\n  root: true,\n  rules: {\n    \"@typescript-eslint/strict-boolean-expressions\": [\"error\"],\n  },\n  parserOptions: {\n    sourceType: \"module\",\n    project: \"tsconfig.json\",\n    tsconfigRootDir: \"./\",\n  },\n};\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n\n# Next.js build output\n.next\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and *not* Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n.vscode/settings.json\n"
  },
  {
    "path": ".npmignore",
    "content": ".vscode\ndist/test/*\nsrc/test/*\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  \"configurations\": [\n    {\n      \"args\": [\n        \"--timeout\",\n        \"999999\",\n        \"--colors\",\n        \"${workspaceFolder}/dist/test/test\"\n      ],\n      \"internalConsoleOptions\": \"openOnSessionStart\",\n      \"name\": \"Mocha Tests (all)\",\n      \"program\": \"${workspaceFolder}/node_modules/mocha/bin/_mocha\",\n      \"request\": \"launch\",\n      \"skipFiles\": [\"<node_internals>/**\"],\n      \"type\": \"node\",\n      \"preLaunchTask\": \"build-dev\"\n    },\n\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Mocha (filtered)\",\n      \"program\": \"${workspaceFolder}/node_modules/mocha/bin/_mocha\",\n      \"args\": [\n        \"--timeout\",\n        \"999999\",\n        \"--colors\",\n        \"${workspaceFolder}/dist/test/test\",\n        \"-f\",\n        \"'${input:filter}'\"\n      ],\n      \"internalConsoleOptions\": \"openOnSessionStart\",\n      \"skipFiles\": [\"<node_internals>/**\"],\n      \"preLaunchTask\": \"build-dev\"\n    }\n  ],\n  \"inputs\": [\n    {\n      \"id\": \"filter\",\n      \"type\": \"promptString\",\n      \"description\": \"Enter a Mocha tests filter\"\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n  \"version\": \"2.0.0\",\n  \"tasks\": [\n    {\n      \"type\": \"npm\",\n      \"label\": \"build-dev\",\n      \"script\": \"build-dev\"\n    }\n  ]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# 3.2.2\n- Fix #76: Add support for TypeScript 4.8\n\n# 3.2.1\n\n- Feature #73: Add a function to unmount a component tree from outside the Forgo\n- Fix #50: Components that returned a fragment saw their `mount` lifecycle\n  method called after the first child element had been created instead of after\n  the render had completed. \n- Fix #70: Calling `component.update()` during a mount lifecycle handler\n  resulted in the component recursively mounting ad infinitum.\n- Fix #75: ESLint plugin `eslint-plugin-import` could not resolve imports of Forgo\n- Add TSX support for custom element tag names. Non-string attributes are not\n  yet supported\n\n# 3.2.0\n\n- #59: Forgo's legacy component syntax (component syntax used through v3.1.1)\n  has been deprecated, and will be removed in v4.0. For more details, please see\n  the deprecation notice on https://forgojs.org.\n- Fix #62: ensure that a child component's `mount()` lifecycle method is only\n  called after its parent has completely finished rendering\n- Feature: Allow components to return `null` or `undefined` from their\n  `render()` method (#39)\n\n# 3.1.1\n\n- Fix: components that changed their root HTML tag were erroneously unmounted\n\n# 3.0.2\n\n- Fix: component teardown left old DOM elements in memory (#47)\n\n# 3.0.0\n\n- Feature: Allow the user to manually add DOM elements to a rendered component without modifying or removing them. This allows e.g., using charting libraries with Forgo. (#42)\n- Fix: allow components & elements to receive falsey `key`s (`0`, `false`, `null`) (#45)\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Jeswin\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": "# forgo\n\nForgo is a 4KB library that makes it super easy to create modern web apps using JSX (like React).\n\nUnlike React, there are very few framework specific patterns and lingo to learn. Everything you already know about DOM APIs and JavaScript will easily carry over.\n\n- Use HTML DOM APIs for accessing elements\n- There are no synthetic events\n- Use closures and ordinary variables for maintaining component state\n- There's no vDOM or DOM diffing\n- Renders are manually triggered\n- Declarative DOM updates\n\n## We'll be tiny. Always.\n\nAll of Forgo's code is in one single TypeScript file. It is a goal of the project to remain within that single file.\n\n## Installation\n\n```\nnpm install forgo\n```\n\n## Starting a Forgo project\n\nThe easiest way to get started is with the 'create-forgo-app' utility. This relies on git, so you should have git installed on your machine.\n\n```sh\nnpx create-forgo-app my-project\n```\n\nIt supports TypeScript too:\n\n```sh\nnpx create-forgo-app my-project --template typescript\n```\n\nAnd then to run it:\n\n```sh\n# Switch to the project directory\ncd my-project\n\n# Run!\nnpm start\n\n# To make a production build\nnpm run build\n```\n\n## A Forgo Component\n\nForgo components are functions that return a Component instance, which has a\n`render()` method that returns JSX. Components hold their state using ordinary\nvariables held in the closure scope of the function (called a Component Constructor).\n\nForgo likes to keep things simple and explicit. It avoids automatic behavior,\nprefers basic functions and variables instead of implicit constructs, and tries\nnot to come between you and the DOM.\n\nHere's the smallest Forgo component you can make:\n\n```jsx\nimport * as forgo from \"forgo\";\n\nconst HelloWorld = () => {\n  return new forgo.Component({\n    render() {\n      return <p>Hello, world!</p>;\n    }\n  });\n};\n```\n\nWhen a component is created (either by being mounted onto the DOM when the page\nloads, or because it was rendered by a component higher up in your app), Forgo\ncalls the Component Constructor to generate the component instance. This is\nwhere you can put closure variables to hold the component's state.\n\nThen Forgo calls the component's `render()` method to generate the HTML that\nForgo will show on the page.\n\nAfter the component's first render, the Constructor won't be called again, but\nthe `render()` method will be called each time the component (or one of its\nancestors) rerenders.\n\nForgo will pass any props (i.e., HTML attributes from your JSX) to both the\nConstructor and the `render()` method.\n\nHere's a bigger example - the component below increments a counter when a button is\npressed, and counts how many seconds the component has been alive.\n\n```jsx\nimport * as forgo from \"forgo\";\n\nconst ClickCounter = (initialProps) => {\n  let seconds = 0; // Just a regular variable, no hooks!\n  let clickCounter = 0;\n\n  const component = new forgo.Component({\n    // Every component has a render() method, which declares what HTML Forgo\n    // needs to generate.\n    render(props) {\n      const { firstName } = props;\n      // You can declare any DOM event handlers you need inside the render()\n      // method.\n      const onclick = (_event: Event) => {\n        // Forgo doesn't know or care how you manage your state. This frees you\n        // to use any library or code pattern that suits your situation, not\n        // only tools designed to integrate with the framework.\n        clickCounter += 1;\n        // When you're ready to rerender, just call component.update(). Manual\n        // updates mean the framework only does what you tell it to, putting you\n        // in control of efficiency and business logic.\n        //\n        // An optional package, forgo-state, can automate this for simple scenarios.\n        component.update();\n      };\n\n      // Forgo uses JSX, like React or Solid, to generate HTML declaratively.\n      // JSX is a special syntax for JavaScript, which means you can treat it\n      // like ordinary code (assign it to variables, type check it, etc.).\n      return (\n        <div>\n          <p>Hello, {firstName}!</p>\n          <button type=\"button\" onclick={onclick}>\n            The button has been clicked {clickCounter} times in {seconds} seconds\n          </button>\n        </div>\n      );\n    }\n  });\n\n  // You can add callbacks to react to lifecycle events,\n  // like mounting and unmounting\n  component.mount(() => {\n    const timeout = setTimeout(() => {\n      seconds++;\n      component.update();\n    }, 1000);\n    component.unmount(() => clearTimeout(timeout));\n  });\n\n  return component;\n};\n```\n\nHere's how the API looks when using TypeScript (which is optional):\n\n```tsx\nimport * as forgo from \"forgo\";\n\n// The constructor generic type accepts the shape of your component's props\nconst HelloWorld = () => {\n  return new forgo.Component({\n    render({ name }) {\n      return <p>Hello, {name}!</p>;\n    }\n  });\n};\n```\n\nIf you assign the component to a variable (such as when adding lifecycle event\nhandlers), you'll need to annotate the generic types on both the constructor and\nthe component. \n\nGeneric props can also be used:\n\n```tsx\nimport * as forgo from \"forgo\";\n\n// Props have to be assigned to the initial props for TSX to recognize the generic\ntype ListProps<T extends string | number> = {\n  data: T[];\n};\n\nconst List = <T extends string | number>(initial: ListProps<T>) =>\n  new forgo.Component<ListProps<T>>({\n    render(props) {\n      return (\n        <ul>\n          {props.data.map((item) => (\n            <li>{item}</li>\n          ))}\n        </ul>\n      );\n    },\n  });\n\nconst App = () =>\n  new forgo.Component({\n    render(props) {\n      return <List data={[1, \"2\", 3]} />;\n    },\n  });\n```\n\n_If you're handy with TypeScript, [we'd love a PR to infer the types!](https://github.com/forgojs/forgo/issues/68)_\n\n```tsx\nimport * as forgo from \"forgo\";\n\ninterface HelloWorldProps {\n  name: string;\n}\nconst HelloWorld = () => {\n  const component = new forgo.Component<HelloWorldProps>({\n    render({ name }) {\n      return <p>Hello, {name}!</p>;\n    }\n  });\n\n  component.mount(() => console.log(\"Mounted!\"));\n\n  return component;\n};\n```\n\n## Launching your components when the page loads\n\nUse the mount() function once your document has loaded.\n\n```js\nimport { mount } from \"forgo\";\n\n// Wait for the page DOM to be ready for changes\nfunction ready(fn) {\n  if (document.readyState !== \"loading\") {\n    fn();\n  } else {\n    document.addEventListener(\"DOMContentLoaded\", fn);\n  }\n}\n\nready(() => {\n  // Attach your app's root component to a specific DOM element\n  mount(<App />, document.getElementById(\"root\"));\n});\n```\n\nInstead of retrieving the DOM element yoursely, you could pass a CSS selector\nand Forgo will find the element for you:\n\n```js\nready(() => {\n  mount(<App />, \"#root\");\n});\n```\n\n## Child Components and Passing Props\n\nProps and children work just like in React and similar frameworks:\n\n```jsx\n// Component Constructors will receive the props passed the *first* time the\n// component renders. But beware! This value won't be updated on later renders.\n// Props passod to the Constructor are useful for one-time setup, but to read\n// the latest props you'll need to use the value passed to render().\nconst Parent = (_initialProps) => {\n  return new forgo.Component({\n    // The props passed here will always be up-to-date.\n    //\n    // All lifecycle methods (render, mount, etc.) receive a reference to the\n    // component. This makes it easy to create reusable logic that works for\n    // many different components.\n    render(_props, _component) {\n      return (\n        <div>\n          <Greeter firstName=\"Jeswin\" />\n          <Greeter firstName=\"Kai\" />\n        </div>\n      );\n    }\n  });\n};\n\nconst Greeter = (_initialProps) => {\n  return new forgo.Component({\n    render(props, _component) {\n      return <div>Hello {props.firstName}</div>;\n    }\n  });\n};\n```\n\nYou can pass any kind of value as a prop - not just strings! You just have to\nuse curly braces instead of quotes:\n\n```jsx\nconst MyComponent = () => {\n  return new forgo.Component({\n    render(_props) {\n      return <NumberComponent myNumber={2} />;\n    }\n  });\n};\n```\n\nYou can have one component wrap JSX provided by another. To do this, just render `props.children`.\n\n```jsx\nconst Parent = () => {\n  return new forgo.Component({\n    render(_props) {\n      return\n        <Child>\n          <p>Hello, world!</p>\n        </Child>\n      )\n    }\n  });\n}\n\nconst Child = () => {\n  return new forgo.Component({\n    render(props) {\n      return (\n        <div>\n          <p>Here's what the parent told us to render:</p>\n          {props.children}\n        </div>\n      )\n    }\n  });\n}\n```\n\n## Reading Form Input Elements\n\nForgo encourages you to use the vanilla DOM API when you need to read form field\nvalues, by directly accessing the DOM elements in the form.\n\nTo access the actual DOM elements corresponding to your markup (and the values\ncontained within them), you need to use the `ref` attribute in the JSX markup of\nthe element you want to reference. An element referenced by the `ref` attribute\nwill have its 'value' property set to the actual DOM element when it gets\ncreated.\n\nHere's an example:\n\n```jsx\nconst MyComponent = (_initialProps) => {\n  // This starts as an empty object. After the element is created, this object\n  // will have a `value` field holding the element.\n  const myInputRef = {};\n\n  return new forgo.Component({\n    render(_props, _component) {\n      const onClick = () => {\n        const inputElement = myInputRef.value;\n        alert(inputElement.value); // Read the text input.\n      };\n\n      return (\n        <div>\n          <input type=\"text\" ref={myInputRef} />\n          <button type=\"button\" onclick={onClick}>Click me!</button>\n        </div>\n      );\n    }\n  });\n};\n```\n\nIf you want, you can bypass Forgo entirely when reading form field values. If\nyou set the `id` field on the form field, then you could use the vanilla DOM API\nto access that element directly:\n\n```jsx\nconst onClick = () => {\n  const inputElement = document.getElementById(\"my-input\");\n  alert(inputElement.value);\n};\n```\n\nLastly, DOM events like key presses and clicks pass the affected element to the\nevent handler as `event.target`:\n\n```jsx\nconst Component = (_initialProps) => {\n  return new forgo.Component({\n    render(_props, _component) {\n      const onInput = (event) => {\n        alert(event.target.value);\n      };\n\n      return (\n        <div>\n          <input type=\"text\" oninput={onInput} />\n        </div>\n      );\n    }\n  });\n};\n```\n\n## Rendering Lists and using Keys\n\nForgo will render any arrays it sees in the JSX. To create a list of elements,\njust use the array's `myArray.map()` method to generate JSX for each item in the array.\n\nEach item in the array may be given a `key` attribute. Keys help Forgo identify\nwhich items in a list have changed, are added, or are removed. While Forgo works\nwell without keys, it is a good idea to add them since it lets Forgo be more\nefficient by only mounting or unmounting components that actually need it.\n\nYou can use any data type for a key strings, numbers or even objects. The key\nvalues only need to be unique. Forgo compares keys using `===` (reference\nequality), so be careful when using mutable objects as keys.\n\nWhen looping over an array, don't use the array index as a key - keys should be\nsomething tied to the specific value being rendered (like a permanent ID field).\nThe same array index might be associated with different values if you reorder\nthe array, and so using the array index as a key will cause unexpected behavior.\n\n```jsx\nconst Parent = () => {\n  return new forgo.Component({\n    render(_props, _component) {\n      const people = [\n        { firstName: \"jeswin\", id: 123 },\n        { firstName: \"kai\", id: 456 },\n      ];\n      return (\n        <div>\n          {people.map((item) => (\n            <Child key={item.id} firstName={item.firstName} />\n          ))}\n        </div>\n      );\n    }\n  });\n};\n\nconst Child = (initialProps) => {\n  return new forgo.Component({\n    render(props) {\n      return <div>Hello {props.firstName}</div>;\n    },\n  });\n};\n```\n\n## Fetching data asynchronously\n\nYour component might need to load data asynchronously (such as making a network\nrequest). Here's how to do that:\n\n```jsx\nexport const InboxComponent = (_initialProps) => {\n  // This will be empty at first, and will get filled in sometime after the\n  // component first mounts.\n  let messages = undefined;\n\n  const component = new forgo.Component({\n    render(_props, _component) {\n      // Messages are empty. Let's fetch them.\n      if (!messages) {\n        return <p>Loading data...</p>;\n      }\n\n      // After messages are fetched, the component will rerender and now we can\n      // show the data.\n      return (\n        <div>\n          <header>Your Inbox</header>\n          <ul>\n            {messages.map((message) => (\n              <li>{message}</li>\n            ))}\n          </ul>\n        </div>\n      );\n    }\n  });\n\n  component.mount(async () => {\n    messages = await fetchMessagesFromServer();\n    component.update();\n  });\n\n  return component;\n};\n```\n\n## The Mount Event\n\nThe mount event is fired just once per component, when the component has just\nbeen created. This is useful for set-up logic like starting a timer, fetching\ndata, or opening a WebSocket.\n\nYou can register multiple mount callbacks, which is useful if you want to have\nreusable logic that you apply to a number of components.\n\n```jsx\nconst Greeter = (_initialProps) => {\n  const component = new forgo.Component({\n    render(_props, _component) {\n      return <div id=\"hello\">Hello {props.firstName}</div>;\n    }\n  });\n\n  component.mount((_props, _component) => {\n    console.log(\"The component has been mounted.\");\n  });\n\n  return component;\n};\n```\n\n## The Unmount Event\n\nA component is unmounted when your app no longer renders it (such as when a\nparent component chooses to display something different, or when an item is\nremoved from a list you're rendering).\n\nWhen a component is unmounted, you might want to do tear-down, like canceling a\ntimer or closing a WebSocket. To do this, you can register unmount callbacks\non your component, which will be called when the component is unmounted.\n\nThe callbacks are passed the current props and the component instance, just like\nthe `render()` method.\n\n```jsx\nconst Greeter = (_initialProps) => {\n  const component = new forgo.Component({\n    render(props, _component) {\n      return <div>Hello {props.firstName}</div>;\n    }\n  });\n\n  component.unmount((props, _component) => {\n    console.log(\"The component has been unloaded.\");\n  });\n\n  return component;\n};\n```\n\n## Skipping renders\n\nSometimes you have a reason why a component shouldn't be rendered right now. For\nexample, if you're using immutable data structures, you may want to only\nrerender if the data structure has changed.\n\nForgo components accept `shouldUpdate` callbacks, which return true/false to\nsignal whether the component should / should not be rerendered. If any\n`shouldUpdate` callbacks return true, the component will be rerendered. If they\nall return false (or if none are registered), the component's `render()` method\nwon't be called, skipping all DOM operations for the component and its\ndecendants.\n\nThe callbacks receive the new props for the proposed render, and the old props\nused in the last render.\n\nUsing `shouldUpdate` is completely optional, and typically isn't necessary.\n\n```jsx\nconst Greeter = (_initialProps) => {\n  const component = new forgo.Component({\n    render(props, component) {\n      return <div>Hello {props.firstName}</div>;\n    }\n  });\n\n  component.shouldUpdate((newProps, oldProps) => {\n    return newProps.firstName !== oldProps.firstName;\n  });\n\n  return component;\n}\n```\n\n## Error handling\n\nForgo lets components define an `error()` method, which is run any time the\ncomponent (or any of its decendants) throws an exception while running the\ncomponent's `render()` method. The error method can return JSX that is rendered\nin place of the render output, to display an error message to the user.\n\nIf no ancestors have an `error()` method registered, the render will abort and\nForgo will print an error to the console.\n\n```jsx\n// Here's a component which throws an error.\nconst BadComponent = () => {\n  return new forgo.Component({\n    render() {\n      throw new Error(\"Some error occurred :(\");\n    }\n  });\n}\n\n// The first ancestor with an error() method defined will catch the error\nconst Parent = (initialProps) => {\n  return new forgo.Component({\n    render() {\n      return (\n        <div>\n          <BadComponent />\n        </div>\n      );\n    },\n    error(props, error, _component) {\n      return (\n        <p>\n          Error in {props.name}: {error.message}\n        </p>\n      );\n    }\n  });\n}\n```\n\n## The AfterRender Event\n\nIf you're an application developer you'll rarely need to use this - it's\nprovided for building libraries that wrap Forgo.\n\nThe `afterRender` event runs after `render()` has been called and the rendered\nelements have been created in the DOM. The callback is passed the previous DOM\nelement the component was attached to, if it changed in the latest render.\n\n```jsx\nconst Greeter = (_initialProps) => {\n  const component = new forgo.Component({\n    render(props, component) {\n      return <div id=\"hello\">Hello {props.firstName}</div>;\n    }\n  });\n\n  component.afterRender((_props, previousNode, _component) => {\n    console.log(\n      `This component is mounted on ${component.__internal.element.node.id}, and was previously mounted on ${previousNode.id}`\n    );\n  });\n\n  return component;\n};\n```\n\n## Passing new props when rerendering\n\nThe most straight forward way to do rerender is by invoking it with `component.update()`, as follows:\n\n```jsx\nconst TodoList = (initialProps) => {\n  let todos = [];\n\n  return new forgo.Component({\n    render(props, component) {\n      const addTodos = (text) => {\n        todos.push(text);\n        component.update();\n      };\n      return (\n        <button type=\"button\" onclick={addTodos}>\n          Add a Todo\n        </button>\n      );\n    }\n  });\n}\n```\n\n`component.update()` may optionally receive new props to use in the render.\nOmitting the props parameter will rerender leave the props unchanged.\n\n```js\nconst newProps = { name: \"Kai\" };\ncomponent.update(newProps);\n```\n\n## Rendering without mounting\n\nForgo also exports a render method that returns the rendered DOM node that could then be manually mounted.\n\n```jsx\nimport { render } from \"forgo\";\n\nconst { node } = render(<Component />);\n\nwindow.addEventListener(\"load\", () => {\n  document.getElementById(\"root\").firstElementChild.replaceWith(node);\n});\n```\n\n## Routing\n\nForgo offers an optional package (`forgo-router`) for handling client-side\nnavigation. Forgo Router is just around 1KB gzipped. Read more at\nhttps://github.com/forgojs/forgo-router\n\nHere's an example:\n\n```jsx\nimport { Router, Link, matchExactUrl, matchUrl } from \"forgo-router\";\n\nconst App = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <Router>\n          <Link href=\"/\">Go to Home Page</Link>\n          {matchExactUrl(\"/\", () => <Home />) ||\n            matchUrl(\"/customers\", () => <Customers />) ||\n            matchUrl(\"/about\", () => <AboutPage />)}\n        </Router>\n      );\n    }\n  });\n}\n```\n\n## Application State Management\n\nForgo offers an optional package (`forgo-state`) with an easy-to-use application\nstate management solution for Forgo. This solves a similar problem to Redux or\nMobX. It's than 1KB gzipped. Read more at https://github.com/forgojs/forgo-state\n\nHere's an example:\n\n```jsx\nimport { bindToStates, defineState } from \"forgo-state\";\n\n// Define one (or more) application state containers.\nconst mailboxState = defineState({\n  username: \"Bender B. Rodriguez\",\n  messages: [],\n  drafts: [],\n  spam: [],\n  unread: 0\n});\n\n// A Forgo component that should react to state changes\nconst MailboxView = () => {\n  const component = new forgo.Component({\n    render() {\n      if (mailboxState.messages.length > 0) {\n        return (\n          <div>\n            {mailboxState.messages.map((m) => <p>{m}</p>)}\n          </div>\n        );\n      }\n\n      return (\n        <div>\n          <p>There are no messages for {mailboxState.username}.</p>\n        </div>\n      );\n    }\n  });\n\n  component.mount(() => updateInbox());\n\n  // MailboxView must change whenever mailboxState changes.\n  //\n  // Under the hood, this registers component.mount() and component.unmount()\n  // even handlers\n  bindToStates([mailboxState], component);\n  return component;\n}\n\nasync function updateInbox() {\n  const data = await fetchInboxData();\n  // The next line causes a rerender of the MailboxView component\n  mailboxState.messages = data;\n}\n```\n\n## Lazy Loading\n\nIf you want to lazy load a component, you can use the community-provided\n`forgo-lazy` package. This is useful for code splitting, where you want the\ninitial page load to be quick (loading the smallest JS possible), and then load\nin more components only when the user needs them. Read more at\nhttps://github.com/jacob-ebey/forgo-lazy\n\nIt's works like this:\n\n```jsx\nimport lazy, { Suspense } from \"forgo-lazy\";\n\nconst LazyComponent = lazy(() => import(\"./lazy-component\"));\n\nconst App = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <Suspense fallback={() => \"Loading...\"}>\n          <LazyComponent title=\"It's that easy :D\" />\n        </Suspense>\n      );\n    }\n  });\n}\n```\n\n## Integrating Forgo into an existing app\n\nForgo can be integrated into an existing web app written with other frameworks\n(React, Vue, etc.), or with lower-level libraries like jQuery.\n\nTo help with that, the `forgo-powertoys` package (less than 1KB in size) exposes\na `rerenderElement()` function which can receive a CSS selector and rerender the\nForgo component associated with that element. This works from outside the Forgo\napp, so you can drive Forgo components using your framework/library of choice.\nRead more at https://github.com/forgojs/forgo-powertoys\n\nHere's an example:\n\n```jsx\nimport { rerenderElement } from \"forgo-powertoys\";\n\n// A forgo component.\nconst LiveScores = () => {\n  return new forgo.Component({\n    render(props) {\n      return <p id=\"live-scores\">Top score is {props.topscore}</p>;\n    }\n  });\n}\n\n// Mount it on a DOM node usual\nwindow.addEventListener(\"load\", () => {\n  mount(<SimpleTimer />, document.getElementById(\"root\"));\n});\n\n// Now you can rerender the component from anywhere, anytime! Pass in the ID of\n// the root element the component returns, as well as new props.\nrerenderElement(\"#live-scores\", { topscore: 244 });\n```\n\n## Server-side Rendering (SSR)\n\nFrom Node.js you can render components to an HTML string with the `forgo-ssr`\npackage. This allows you to prerender components on the server, from server-side\nframeworks like Koa, Express etc. Read more at\nhttps://github.com/forgojs/forgo-ssr\n\nHere's an example:\n\n```jsx\nimport render from \"forgo-ssr\";\n\n// A forgo component.\nconst MyComponent = () => {\n  return new forgo.Component({\n    render() {\n      return <div>Hello world</div>;\n    }\n  });\n}\n\n// Get the html (string) and serve it via koa, express etc.\nconst html = render(<MyComponent />);\n```\n\n## Manually adding elements to the DOM\n\nForgo allows you to use the built-in browser DOM API to insert elements into the\nDOM tree rendered by a Forgo component. Forgo will ignore these elements. This\nis useful for working with charting libraries, such as D3.\n\nIf you add unmanaged nodes as siblings to nodes which Forgo manages, Forgo\npushes the unmanaged nodes towards the bottom of the sibling list when managed\nnodes are added and removed. If you don't add/remove managed nodes, the\nunmanaged nodes will stay in their original positions.\n\n### ApexCharts example\n\n[Code Sandbox](https://codesandbox.io/s/forgo-apexcharts-demo-ulkqfe?file=/src/index.tsx) for this example\n\n```jsx\nconst App = () => {\n  const chartElement = {};\n\n  const component = new forgo.Component({\n    render(_props, component) {\n      const now = new Date();\n      return (\n        <div>\n          <p>\n            This component continually rerenders. Forgo manages the timestamp,\n            but delegates control of the chart to ApexCharts.\n          </p>\n          <div ref={chartElement}></div>\n          <p>\n            The current time is:{\" \"}\n            <time datetime={now.toISOString()}>{now.toLocaleString()}</time>\n          </p>\n        </div>\n      );\n    }\n  });\n\n  component.mount(() => {\n    const chartOptions = {\n      chart: {\n        type: \"line\",\n      },\n      series: [\n        {\n          name: \"sales\",\n          data: [30, 40, 35, 50, 49, 60, 70, 91, 125],\n        },\n      ],\n      xaxis: {\n        categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999],\n      },\n    };\n\n    const chart = new ApexCharts(chartElement.value, chartOptions);\n\n    chart.render();\n\n    const interval = setInterval(() => component.update(), 1_000);\n    component.unmount(() => clearInterval(interval));\n  });\n\n  return component;\n};\n```\n\n## Try it out on CodeSandbox\n\nYou can try the [Todo List app with Forgo](https://codesandbox.io/s/forgo-todos-javascript-1oi9b) on CodeSandbox.\n\nOr if you prefer TypeScript, try [Forgo TodoList in TypeScript](https://codesandbox.io/s/forgo-todos-typescript-9v0iy).\n\nThere is also an example for using [Forgo with forgo-router](https://codesandbox.io/s/forgo-router-typescript-px4sg).\n\n## Building\n\nMost users should use create-forgo-app to create the project skeleton - in which\ncase all of this is already set up for you. This is the easiest way to get\nstarted.\n\nIf you want to stand up a project manually, we'll cover webpack-specific\nconfiguration here. Other bundlers would need similar configuration.\n\n### esbuild-loader with JavaScript/JSX\n\nAdd these lines to webpack.config.js:\n\n```js\nmodule.exports = {\n  // remaining config omitted for brevity.\n  module: {\n    rules: [\n      {\n        test: /\\.(js|jsx)$/,\n        exclude: /node_modules/,\n        loader: \"esbuild-loader\",\n        options: {\n          loader: \"jsx\",\n          target: \"es2015\",\n          jsxFactory: \"forgo.createElement\",\n          jsxFragment: \"forgo.Fragment\",\n        },\n      },\n    ],\n  },\n};\n```\n\n### esbuild-loader with TypeScript/TSX\n\nAdd these lines to webpack.config.js:\n\n```js\nmodule.exports = {\n  // remaining config omitted for brevity.\n  module: {\n    rules: [\n      {\n        test: /\\.tsx?$/,\n        exclude: /node_modules/,\n        loader: \"esbuild-loader\",\n        options: {\n          loader: \"tsx\",\n          target: \"es2015\",\n          jsxFactory: \"forgo.createElement\",\n          jsxFragment: \"forgo.Fragment\",\n        },\n      },\n    ],\n  },\n};\n```\n\nWhile using TypeScript, also add the following lines to your tsconfig.json. This lets you do `tsc --noEmit` for type checking, which esbuild-loader doesn't do.\n\nAdd these lines to tsconfig.json:\n\n```json\n{\n  \"compilerOptions\": {\n    \"jsx\": \"react\",\n    \"jsxFactory\": \"forgo.createElement\",\n    \"jsxFragmentFactory\": \"forgo.Fragment\"\n  }\n}\n```\n\n### babel-loader with JSX\n\nThis is slower than esbuild-loader, so use only as needed.\n\nAdd these lines to webpack.config.js:\n\n```js\nmodule.exports = {\n  // remaining config omitted for brevity.\n  module: {\n    rules: [\n      {\n        test: /\\.(js|jsx)$/,\n        exclude: /node_modules/,\n        use: [\"babel-loader\"],\n      },\n    ],\n  },\n};\n```\n\nAdd these lines to babel.config.json:\n\n```json\n{\n  \"presets\": [\"@babel/preset-env\", \"@babel/preset-react\"],\n  \"plugins\": [\n    [\"@babel/plugin-transform-react-jsx\", { \"pragma\": \"forgo.createElement\" }]\n  ]\n}\n```\n\n### TSX with ts-loader\n\nAdd these lines to webpack.config.js:\n\n```js\nmodule.exports = {\n  // remaining config omitted for brevity.\n  module: {\n    rules: [\n      {\n        test: /\\.tsx?$/,\n        use: \"ts-loader\",\n        exclude: /node_modules/,\n      },\n    ],\n  },\n};\n```\n\nAdd these lines to tsconfig.json:\n\n```json\n{\n  \"compilerOptions\": {\n    \"jsx\": \"react\",\n    \"jsxFactory\": \"forgo.createElement\",\n    \"jsxFragmentFactory\": \"forgo.Fragment\"\n  }\n}\n```\n\n## Core Team\n\n- [github/jeswin](https://github.com/jeswin)\n- [github/spiffytech](https://github.com/spiffytech)\n\n## Getting Help\n\nIf you find issues, please file a bug on\n[Github](https://github.com/forgojs/forgo/issues). You can also reach out to us\nvia Twitter (@forgojs).\n\n## Deprecation of legacy component syntax is 3.2.0\nIn version 3.2.0, Forgo introduced a new syntax for components. This change\nmakes Forgo easier to extend with reusable libraries, and makes it\nstraightforward to colocate logic that spans mounts & unmounts.\n\nThe legacy component syntax will be removed in v4.0. Until then, Forgo will\nprint a warning to the console whenever it sees a legacy component. You can\nsuppress these warnings by setting `window.FORGO_NO_LEGACY_WARN = true;`.\n\n### Migrating\nForgo components are now instances of the `Component` class, rather than\nfreestanding object values. The `new Component` constructor accepts an object\nholding a `render()` an optional `error()` method. All other methods have been\nconverted to lifecycle methods on the component instance. You may register\nmultiple handlers for each lifecycle event, and you may register new handlers\nfrom inside a handler (e.g., a mount handler that registers its own unmount\nlogic).\n\n`args` has been replaced by a reference to the component instance, in all\nlifecycle event handlers. This simplifies writing reusable component logic.\n\nThe `error()` method now receives the error object as a function parameter,\nrather than as a property on `args`.\n\nThe `afterRender` lifecycle event now receives the `previousNode` as a function\nparameter, instead of a property on `args`.\n\nBefore:\n```jsx\nconst MyComponent = () => {\n  return {\n    render() {},\n    error() {},\n    mount() {},\n    unmount() {},\n    shouldUpdate() {},\n    afterRender() {},\n  };\n}\n```\n\nAfter:\n```jsx\nconst MyComponent = () => {\n  const component = new Component({\n    render() {},\n    error() {}\n  });\n\n  component.mount(() => {});\n  component.unmount(() => {});\n  component.shouldUpdate(() => {});\n  component.afterRender(() => {});\n\n  return component;\n}\n```\n\n## Breaking changes in 2.0\n\nForgo 2.0 drops support for the new JSX transform introduced via \"jsx-runtime\".\nThis never worked with esbuild loader, and more importantly doesn't play well\nwith ES modules. If you were using this previously, switch to the configurations\ndiscussed above."
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"forgo\",\n  \"version\": \"4.1.7\",\n  \"main\": \"./dist/forgo.min.js\",\n  \"type\": \"module\",\n  \"author\": \"Jeswin Kumar<jeswinpk@agilehead.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/forgojs/forgo\"\n  },\n  \"exports\": \"./dist/forgo.min.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"devDependencies\": {\n    \"@types/jsdom\": \"^21.1.0\",\n    \"@types/mocha\": \"^10.0.1\",\n    \"@types/should\": \"^13.0.0\",\n    \"@types/source-map-support\": \"^0.5.6\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.52.0\",\n    \"@typescript-eslint/parser\": \"^5.52.0\",\n    \"esbuild\": \"^0.17.8\",\n    \"eslint\": \"^8.34.0\",\n    \"jsdom\": \"^21.1.0\",\n    \"mocha\": \"^10.2.0\",\n    \"rimraf\": \"^4.1.2\",\n    \"should\": \"^13.2.3\",\n    \"source-map-support\": \"^0.5.21\",\n    \"typescript\": \"^4.9.5\"\n  },\n  \"scripts\": {\n    \"clean\": \"rimraf ./dist\",\n    \"build\": \"npm run clean && npx tsc --emitDeclarationOnly && npx esbuild ./src/index.ts --minify --bundle --format=esm --sourcemap --target=es2015 --outfile=dist/forgo.min.js\",\n    \"build-dev\": \"npx tsc\",\n    \"test\": \"npx tsc && npx mocha dist/test/test.js\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "/*\n  A type that wraps a reference.\n*/\nexport type ForgoRef<T> = {\n  value?: T;\n};\n\n/*\n  We have two types of elements:\n  1. DOM Elements\n  2. Component Elements\n*/\nexport type ForgoElementBaseProps = {\n  children?: ForgoNode | ForgoNode[];\n  ref?: ForgoRef<Element>;\n};\n\n// DOM elements have the additional fields\nexport type ForgoDOMElementProps = {\n  xmlns?: string;\n  dangerouslySetInnerHTML?: { __html: string };\n} & ForgoElementBaseProps;\n\n// Since we'll set any attribute the user passes us, we need to be sure not to\n// set Forgo-only attributes that don't make sense to appear in the DOM\nconst suppressedAttributes = [\"ref\", \"dangerouslySetInnerHTML\"];\n\nexport type ForgoSimpleComponentCtor<TProps extends object> = (\n  props: TProps & ForgoElementBaseProps\n) => ForgoLegacyComponent<TProps>;\n\nexport type ForgoNewComponentCtor<TProps extends object> = (\n  props: TProps & ForgoElementBaseProps\n) => Component<TProps>;\n\nexport type ForgoElementArg = {\n  node?: ChildNode;\n  componentIndex: number;\n};\n\n/*\n  A ForgoNode is the output of the render() function.\n  It can represent:\n  - a primitive type which becomes a DOM Text Node\n  - a DOM Element\n  - or a Component.\n \n  If the ForgoNode is a string, number etc, it's a primitive type.\n  eg: \"hello\"\n\n  If ForgoNode has a type property which is a string, it represents a native DOM element.\n  eg: The type will be \"div\" for <div>Hello</div>\n\n  If the ForgoElement represents a Component, then the type points to a ForgoComponentCtor.\n  eg: The type will be MyComponent for <MyComponent />\n*/\nexport type ForgoElementBase<TProps extends ForgoElementBaseProps> = {\n  key?: any;\n  props: TProps;\n  __is_forgo_element__: true;\n};\n\nexport type ForgoDOMElement<TProps extends ForgoDOMElementProps> =\n  ForgoElementBase<TProps> & {\n    type: string;\n  };\n\nexport type ForgoComponentElement<TProps extends ForgoElementBaseProps> =\n  ForgoElementBase<TProps> & {\n    type: ForgoNewComponentCtor<TProps>;\n  };\n\nexport type ForgoFragment = {\n  type: typeof Fragment;\n  props: { children?: ForgoNode | ForgoNode[] };\n  __is_forgo_element__: true;\n};\n\nexport type ForgoElement<TProps extends ForgoElementBaseProps> =\n  | ForgoDOMElement<TProps>\n  | ForgoComponentElement<TProps>;\n\nexport type ForgoNonEmptyPrimitiveNode =\n  | string\n  | number\n  | boolean\n  | object\n  | bigint;\n\nexport type ForgoPrimitiveNode = ForgoNonEmptyPrimitiveNode | null | undefined;\n\n/**\n * Anything renderable by Forgo, whether from an external source (e.g.,\n * component.render() output), or internally (e.g., DOM nodes)\n */\nexport type ForgoNode = ForgoPrimitiveNode | ForgoElement<any> | ForgoFragment;\n\n/*\n  Forgo stores Component state on the element on which it is mounted.\n\n  Say Custom1 renders Custom2 which renders Custom3 which renders <div>Hello</div>. \n  In this case, the components Custom1, Custom2 and Custom3 are stored on the div.\n \n  You can also see that it gets passed around as pendingStates in the render methods. \n  That's because when Custom1 renders Custom2, there isn't a real DOM node available to attach the state to. \n  So the states are passed around until the last component renders a real DOM node or nodes.\n\n  In addition it holds a bunch of other things. \n  Like for example, a key which uniquely identifies a child element when rendering a list.\n*/\nexport type NodeAttachedComponentState<TProps extends ForgoElementBaseProps> = {\n  key?: any;\n  ctor: ForgoNewComponentCtor<TProps> | ForgoSimpleComponentCtor<TProps>;\n  component: Component<TProps>;\n  props: TProps;\n  nodes: ChildNode[];\n  isMounted: boolean;\n};\n\n/*\n  This is the state data structure which gets stored on a node.  \n  See explanation for NodeAttachedComponentState<TProps>\n*/\nexport type NodeAttachedState = {\n  key?: string | number;\n  props?: { [key: string]: any };\n  components: NodeAttachedComponentState<any>[];\n  style?: { [key: string]: any };\n  deleted?: boolean;\n};\n\n// CSS types lifted from preact.\nexport type DOMCSSProperties = {\n  [key in keyof Omit<\n    CSSStyleDeclaration,\n    | \"item\"\n    | \"setProperty\"\n    | \"removeProperty\"\n    | \"getPropertyValue\"\n    | \"getPropertyPriority\"\n  >]?: string | number | null | undefined;\n};\nexport type AllCSSProperties = {\n  [key: string]: string | number | null | undefined;\n};\nexport interface CSSProperties extends AllCSSProperties, DOMCSSProperties {\n  cssText?: string | null;\n}\n\n/*\n  The following adds support for injecting test environment objects.\n  Such as JSDOM.\n*/\nexport type ForgoEnvType = {\n  window: Window;\n  document: Document;\n  __internal: {\n    HTMLElement: typeof HTMLElement;\n    Text: typeof Text;\n  };\n};\n\n/**\n * Nodes will be created as detached DOM nodes, and will not be attached to the parent\n */\nexport type DetachedNodeInsertionOptions = {\n  type: \"detached\";\n};\n\n/**\n * Instructs the renderer to search for an existing node to modify or replace,\n * before creating a new node.\n */\nexport type SearchableNodeInsertionOptions = {\n  type: \"new-component\";\n  /**\n   * The element that holds the previously-rendered version of this component\n   */\n  parentElement: Element;\n  /**\n   * Where under the parent's children to find the start of this component\n   */\n  currentNodeIndex: number;\n  /**\n   * How many elements after currentNodeIndex belong to the element we're\n   * searching\n   */\n  length: number;\n};\n\n/**\n * Decides how the called function attaches nodes to the supplied parent\n */\nexport type NodeInsertionOptions =\n  | DetachedNodeInsertionOptions\n  | SearchableNodeInsertionOptions;\n\n/*\n  Result of the render functions.\n*/\nexport type RenderResult = {\n  nodes: ChildNode[];\n  pendingMounts: (() => void)[];\n};\n\nexport type DeletedNode = {\n  node: ChildNode;\n};\n\ndeclare global {\n  interface ChildNode {\n    __forgo?: NodeAttachedState;\n    __forgo_deletedNodes?: DeletedNode[];\n  }\n}\n\n/*\n  Fragment constructor.\n  We simply use it as a marker in jsx-runtime.\n*/\nexport const Fragment: unique symbol = Symbol.for(\"FORGO_FRAGMENT\");\n\n/*\n  HTML Namespaces\n*/\nconst HTML_NAMESPACE = \"http://www.w3.org/1999/xhtml\";\nconst MATH_NAMESPACE = \"http://www.w3.org/1998/Math/MathML\";\nconst SVG_NAMESPACE = \"http://www.w3.org/2000/svg\";\n\nconst MISSING_COMPONENT_INDEX = -1;\nconst MISSING_NODE_INDEX = -1;\n\n/*\n  These come from the browser's Node interface, which defines an enum of node\n  types. We'd like to just reference Node.<whatever>, but JSDOM makes us jump\n  through hoops to do that because it hates adding new globals. Getting around\n  that is more complex, and more bytes on the wire, than just hardcoding the\n  constants we care about.\n*/\nconst ELEMENT_NODE_TYPE = 1;\nconst TEXT_NODE_TYPE = 3;\nconst COMMENT_NODE_TYPE = 8;\n\n/**\n * These are methods that a component may implement. Every component is required\n * to have a render method.\n * 1. render() returns the actual DOM to render.\n * 2. error() is called when this component, or one of its children, throws an\n *    error.\n */\nexport interface ForgoComponentMethods<TProps extends object> {\n  render: (\n    props: TProps & ForgoElementBaseProps,\n    component: Component<TProps>\n  ) => ForgoNode | ForgoNode[];\n  error?: (\n    props: TProps & ForgoElementBaseProps,\n    error: unknown,\n    component: Component<TProps>\n  ) => ForgoNode;\n}\n\n/**\n * This type gives us an exhaustive type check, guaranteeing that if we add a\n * new lifecycle event to the array, any types that can be derived from that\n * information will fail to typecheck until they handle the new event.\n */\ntype ComponentEventListenerBase = {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  [event in keyof typeof lifecycleEmitters]: Array<Function>;\n};\n/**\n * It'd be nice if we could just use ComponentEventListenerBase, but the\n * shouldUpdate event gets processed differently, so we need a way to specify\n * that some event listeners have non-void return types\n */\n// TODO: figure out if TS gets angry if the user passes an async function as an\n// event listener. Maybe we need to default to unknown instead of void for the\n// return type?\ninterface ComponentEventListeners<TProps extends object>\n  extends ComponentEventListenerBase {\n  mount: Array<\n    (\n      props: TProps & ForgoElementBaseProps,\n      component: Component<TProps>\n    ) => void\n  >;\n  unmount: Array<\n    (\n      props: TProps & ForgoElementBaseProps,\n      component: Component<TProps>\n    ) => void\n  >;\n  afterRender: Array<\n    (\n      props: TProps & ForgoElementBaseProps,\n      previousNode: ChildNode | undefined,\n      component: Component<TProps>\n    ) => void\n  >;\n  shouldUpdate: Array<\n    (\n      newProps: TProps & ForgoElementBaseProps,\n      oldProps: TProps & ForgoElementBaseProps,\n      component: Component<TProps>\n    ) => boolean\n  >;\n}\n\ninterface ComponentInternal<TProps extends object> {\n  unmounted: boolean;\n  registeredMethods: ForgoComponentMethods<TProps>;\n  eventListeners: ComponentEventListeners<TProps>;\n  element: ForgoElementArg;\n}\n\nconst lifecycleEmitters = {\n  mount<TProps extends object>(\n    component: Component<TProps>,\n    props: TProps\n  ): void {\n    component.__internal.eventListeners.mount.forEach((cb) =>\n      cb(props, component)\n    );\n  },\n  unmount<TProps extends object>(component: Component<TProps>, props: TProps) {\n    component.__internal.eventListeners.unmount.forEach((cb) =>\n      cb(props, component)\n    );\n  },\n  shouldUpdate<TProps extends object>(\n    component: Component<TProps>,\n    newProps: TProps,\n    oldProps: TProps\n  ): boolean {\n    // Always rerender unless we have a specific reason not to\n    if (component.__internal.eventListeners.shouldUpdate.length === 0)\n      return true;\n\n    return component.__internal.eventListeners.shouldUpdate\n      .map((cb) => cb(newProps, oldProps, component))\n      .some(Boolean);\n  },\n  afterRender<TProps extends object>(\n    component: Component<TProps>,\n    props: TProps,\n    previousNode: ChildNode | undefined\n  ) {\n    component.__internal.eventListeners.afterRender.forEach((cb) =>\n      cb(props, previousNode, component)\n    );\n  },\n};\n\n/**\n * This class represents your component. It holds lifecycle methods and event\n * listeners. You may pass it around your application and to 3rd-party libraries\n * to build reusable logic.\n */\nexport class Component<TProps extends object> {\n  /** @internal */\n  public __internal: ComponentInternal<TProps>;\n\n  /**\n   * @params methods The render method is mandatory. It receives your current\n   * props and returns JSX that Forgo will render to the page. Other methods are\n   * optional. See the forgojs.org for more details.\n   */\n  constructor(registeredMethods: ForgoComponentMethods<TProps>) {\n    this.__internal = {\n      registeredMethods,\n      unmounted: false,\n      eventListeners: {\n        afterRender: [],\n        mount: [],\n        unmount: [],\n        shouldUpdate: [],\n      },\n      element: {\n        componentIndex: MISSING_COMPONENT_INDEX,\n      },\n    };\n  }\n\n  public update(props?: TProps & ForgoElementBaseProps) {\n    // TODO: When we do our next breaking change, there's no reason for this to\n    // return anything, but we need to leave the behavior in while we have our\n    // compatibility layer.\n    return rerender(this.__internal.element, props);\n  }\n\n  public mount(listener: ComponentEventListeners<TProps>[\"mount\"][number]) {\n    this.__internal.eventListeners[\"mount\"].push(listener as any);\n  }\n\n  public unmount(listener: ComponentEventListeners<TProps>[\"unmount\"][number]) {\n    this.__internal.eventListeners[\"unmount\"].push(listener as any);\n  }\n\n  public shouldUpdate(\n    listener: ComponentEventListeners<TProps>[\"shouldUpdate\"][number]\n  ) {\n    this.__internal.eventListeners[\"shouldUpdate\"].push(listener as any);\n  }\n\n  public afterRender(\n    listener: ComponentEventListeners<TProps>[\"afterRender\"][number]\n  ) {\n    this.__internal.eventListeners[\"afterRender\"].push(listener as any);\n  }\n}\n\n/**\n * jsxFactory function\n */\nexport function createElement<\n  TProps extends ForgoDOMElementProps & { key?: any }\n>(\n  type:\n    | string\n    | ForgoNewComponentCtor<TProps>\n    | ForgoSimpleComponentCtor<TProps>,\n  props: TProps,\n  ...args: any[]\n) {\n  props = props ?? {};\n  props.children =\n    args.length > 1\n      ? flatten(Array.from(args))\n      : args.length === 1\n      ? flatten(args[0])\n      : undefined;\n\n  const key = props.key ?? undefined;\n  return { type, props, key, __is_forgo_element__: true };\n}\n\nexport const h = createElement;\n\n/*\n  HACK: Chrome fires onblur (if defined) immediately after a node.remove().\n  This is bad news for us, since a rerender() inside the onblur handler \n  will run on an unattached node. So, disable onblur if node is set to be removed.\n*/\nfunction handlerDisabledOnNodeDelete(node: ChildNode, value: any) {\n  return (e: any) => {\n    if (node.__forgo === undefined || !node.__forgo.deleted) {\n      return value(e);\n    }\n  };\n}\n\n/**\n * Creates everything needed to run forgo, wrapped in a closure holding e.g.,\n * JSDOM-specific environment overrides used in tests\n */\nexport function createForgoInstance(customEnv: any) {\n  const env: ForgoEnvType = customEnv;\n  env.__internal = env.__internal ?? {\n    Text: (env.window as any).Text,\n    HTMLElement: (env.window as any).HTMLElement,\n  };\n\n  /**\n    * This is the main render function.\n\n    * @param forgoNode The node to render. Can be any value renderable by Forgo,\n    * not just DOM nodes.\n    * @param insertionOptions Which nodes need to be replaced by the new\n    * node(s), or whether the new node should be created detached from the DOM\n    * (without replacement). \n    * @param statesAwaitingAttach The list of Component State objects which will\n    * be attached to the element.\n    */\n  function internalRender(\n    forgoNode: ForgoNode | ForgoNode[],\n    insertionOptions: NodeInsertionOptions,\n    statesAwaitingAttach: NodeAttachedComponentState<any>[],\n    mountOnPreExistingDOM: boolean\n  ): RenderResult {\n    // Array of Nodes, or Fragment\n    if (Array.isArray(forgoNode) || isForgoFragment(forgoNode)) {\n      return renderArray(\n        flatten(forgoNode),\n        insertionOptions,\n        statesAwaitingAttach,\n        mountOnPreExistingDOM\n      );\n    }\n    // Primitive Nodes\n    else if (!isForgoElement(forgoNode)) {\n      return renderNonElement(\n        forgoNode,\n        insertionOptions,\n        statesAwaitingAttach\n      );\n    }\n    // HTML Element\n    else if (isForgoDOMElement(forgoNode)) {\n      return renderDOMElement(\n        forgoNode,\n        insertionOptions,\n        statesAwaitingAttach,\n        mountOnPreExistingDOM\n      );\n    }\n    // Component\n    else {\n      const result = renderComponent(\n        forgoNode,\n        insertionOptions,\n        statesAwaitingAttach,\n        mountOnPreExistingDOM\n      );\n      // In order to prevent issue #50 (Fragments having mount() called before\n      // *all* child elements have finished rendering), we delay calling mount\n      // until a subtree's render has completed\n      //\n      // Ideally this would encompass both mounts and unmounts, but an unmounted\n      // component doesn't get `renderComponent()` called on it, so we need to\n      // continue unmounting inside each of the type-specific render functions.\n      // That's fine since the problem is elements not existing at mount time,\n      // whereas unmount timing isn't sensitive to that.\n      result.pendingMounts.forEach((fn) => fn());\n      result.pendingMounts.length = 0;\n      return result;\n    }\n  }\n\n  /*\n    Render a string.\n   * Such as in the render function below:\n   * function MyComponent() {\n   *   return new forgo.Component({\n   *     render() {\n   *       return \"Hello world\"\n   *     }\n   *   })\n   * }\n   */\n  function renderNonElement(\n    forgoNode: ForgoPrimitiveNode,\n    insertionOptions: NodeInsertionOptions,\n    statesAwaitingAttach: NodeAttachedComponentState<any>[]\n  ): RenderResult {\n    // Text and comment nodes will always be recreated (why?).\n    let node: ChildNode;\n    if (forgoNode === null || forgoNode === undefined) {\n      node = env.document.createComment(\"null component render\");\n    } else {\n      node = env.document.createTextNode(stringOfNode(forgoNode));\n    }\n    let oldComponentState: NodeAttachedComponentState<any>[] | undefined =\n      undefined;\n\n    // We have to find a node to replace.\n    if (insertionOptions.type === \"new-component\") {\n      const childNodes = insertionOptions.parentElement.childNodes;\n\n      // If we're searching in a list, we replace if the current node is a text node.\n      if (insertionOptions.length) {\n        const targetNode = childNodes[insertionOptions.currentNodeIndex];\n        if (\n          targetNode.nodeType === TEXT_NODE_TYPE ||\n          targetNode.nodeType === COMMENT_NODE_TYPE\n        ) {\n          targetNode.replaceWith(node);\n          oldComponentState = getForgoState(targetNode)?.components;\n        } else {\n          const nextNode = childNodes[insertionOptions.currentNodeIndex];\n          insertionOptions.parentElement.insertBefore(node, nextNode);\n        }\n      }\n      // There are no target nodes available.\n      else if (\n        childNodes.length === 0 ||\n        insertionOptions.currentNodeIndex === 0\n      ) {\n        insertionOptions.parentElement.prepend(node);\n      } else {\n        const nextNode = childNodes[insertionOptions.currentNodeIndex];\n        insertionOptions.parentElement.insertBefore(node, nextNode);\n      }\n    }\n\n    syncAttrsAndState(forgoNode, node, true, statesAwaitingAttach);\n    unmountComponents(statesAwaitingAttach, oldComponentState);\n    return {\n      nodes: [node],\n      pendingMounts: [\n        () => mountComponents(statesAwaitingAttach, oldComponentState),\n      ],\n    };\n  }\n\n  /*\n    Render a DOM element. Will find + update an existing DOM element (if\n    appropriate), or insert a new element.\n  \n    Such as in the render function below:\n    function MyComponent() {\n      return {\n        render() {\n          return <div>Hello world</div>\n        }\n      }\n    }\n  */\n  function renderDOMElement<TProps extends ForgoDOMElementProps>(\n    forgoElement: ForgoDOMElement<TProps>,\n    insertionOptions: NodeInsertionOptions,\n    statesAwaitingAttach: NodeAttachedComponentState<any>[],\n    mountOnPreExistingDOM: boolean\n  ): RenderResult {\n    // We need to create a detached node\n    if (insertionOptions.type === \"detached\") {\n      return addElement(undefined, null);\n    }\n    // We have to find a node to replace.\n    else {\n      const childNodes = insertionOptions.parentElement.childNodes;\n\n      if (insertionOptions.length) {\n        const searchResult = findReplacementCandidateForElement(\n          forgoElement,\n          insertionOptions.parentElement,\n          insertionOptions.currentNodeIndex,\n          insertionOptions.length\n        );\n\n        if (searchResult.found) {\n          return renderExistingElement(\n            searchResult.index,\n            childNodes,\n            insertionOptions\n          );\n        }\n      }\n\n      return addElement(\n        insertionOptions.parentElement,\n        childNodes[insertionOptions.currentNodeIndex]\n      );\n    }\n\n    function renderChildNodes(parentElement: Element) {\n      // If the user gave us exact HTML to stuff into this parent, we can\n      // skip/ignore the usual rendering logic\n      if (forgoElement.props.dangerouslySetInnerHTML) {\n        parentElement.innerHTML =\n          forgoElement.props.dangerouslySetInnerHTML.__html;\n      } else {\n        // Coerce children to always be an array, for simplicity\n        const forgoChildren = flatten([forgoElement.props.children]).filter(\n          // Children may or may not be specified\n          (x) => x !== undefined && x !== null\n        );\n\n        // Make sure that if the user prepends non-Forgo DOM children under this\n        // parent that we start with the correct offset, otherwise we'll do DOM\n        // transformations that don't make any sense for the given input.\n        const firstForgoChildIndex = Array.from(\n          parentElement.childNodes\n        ).findIndex((child) => getForgoState(child));\n        // Each node we render will push any leftover children further down the\n        // parent's list of children. After rendering everything, we can clean\n        // up anything extra. We'll know what's extra because all nodes we want\n        // to preserve come before this index.\n        let lastRenderedNodeIndex =\n          firstForgoChildIndex === -1 ? 0 : firstForgoChildIndex;\n        for (const forgoChild of forgoChildren) {\n          const { nodes: nodesAfterRender } = internalRender(\n            forgoChild,\n            {\n              type: \"new-component\",\n              parentElement,\n              currentNodeIndex: lastRenderedNodeIndex,\n              length: parentElement.childNodes.length - lastRenderedNodeIndex,\n            },\n            [],\n            mountOnPreExistingDOM\n          );\n          // Continue down the children list to wherever's right after the stuff\n          // we just added. Because users are allowed to add arbitrary stuff to\n          // the DOM manually, we can't just jump by the count of rendered\n          // elements, since that's the count of *managed* elements, which might\n          // be interspersed with unmanaged elements that we also need to skip\n          // past.\n          if (nodesAfterRender.length) {\n            while (\n              parentElement.childNodes[lastRenderedNodeIndex] !==\n              nodesAfterRender[nodesAfterRender.length - 1]\n            ) {\n              lastRenderedNodeIndex += 1;\n            }\n            // Move the counter *past* the last node we inserted. E.g., if we just\n            // inserted our first node, we need to increment from 0 -> 1, where\n            // we'll start searching for the next thing we insert\n            lastRenderedNodeIndex += 1;\n            // If we're updating an existing DOM element, it's possible that the\n            // user manually added some DOM nodes somewhere in the middle of our\n            // managed nodes. If that happened, we need to scan forward until we\n            // pass them and find the next managed node, which we'll use as the\n            // starting point for whatever we render next. We still need the +1\n            // above to make sure we always progress the index, in case this is\n            // our first render pass and there's nothing to scan forward to.\n            while (lastRenderedNodeIndex < parentElement.childNodes.length) {\n              if (\n                getForgoState(parentElement.childNodes[lastRenderedNodeIndex])\n              ) {\n                break;\n              }\n              lastRenderedNodeIndex += 1;\n            }\n          }\n        }\n\n        // Remove all nodes that don't correspond to the rendered output of a\n        // live component\n        markNodesForUnloading(\n          parentElement.childNodes,\n          lastRenderedNodeIndex,\n          parentElement.childNodes.length\n        );\n      }\n    }\n\n    /**\n     * If we're updating an element that was rendered in a previous render,\n     * reuse the same DOM element. Just sync its children and attributes.\n     */\n    function renderExistingElement(\n      insertAt: number,\n      childNodes: NodeListOf<ChildNode>,\n      insertionOptions: SearchableNodeInsertionOptions\n    ): RenderResult {\n      // Get rid of unwanted nodes.\n      markNodesForUnloading(\n        childNodes,\n        insertionOptions.currentNodeIndex,\n        insertAt\n      );\n\n      const targetElement = childNodes[\n        insertionOptions.currentNodeIndex\n      ] as Element;\n\n      const oldComponentState = getForgoState(targetElement)?.components;\n\n      syncAttrsAndState(\n        forgoElement,\n        targetElement,\n        false,\n        statesAwaitingAttach\n      );\n\n      renderChildNodes(targetElement);\n      unloadMarkedNodes(targetElement, statesAwaitingAttach);\n      unmountComponents(statesAwaitingAttach, oldComponentState);\n\n      return {\n        nodes: [targetElement],\n        pendingMounts: [\n          () => mountComponents(statesAwaitingAttach, oldComponentState),\n        ],\n      };\n    }\n\n    function addElement(\n      parentElement: Element | undefined,\n      oldNode: ChildNode | null\n    ): RenderResult {\n      const newElement = createElement(forgoElement, parentElement);\n\n      if (parentElement) {\n        parentElement.insertBefore(newElement, oldNode);\n      }\n\n      if (forgoElement.props.ref) {\n        forgoElement.props.ref.value = newElement;\n      }\n\n      syncAttrsAndState(forgoElement, newElement, true, statesAwaitingAttach);\n\n      renderChildNodes(newElement);\n      unmountComponents(statesAwaitingAttach, undefined);\n\n      return {\n        nodes: [newElement],\n        pendingMounts: [() => mountComponents(statesAwaitingAttach, undefined)],\n      };\n    }\n  }\n\n  /*\n    Render a Component.\n    Such as <MySideBar size=\"large\" />\n  */\n  function renderComponent<TProps extends ForgoDOMElementProps>(\n    forgoComponent: ForgoComponentElement<TProps>,\n    insertionOptions: NodeInsertionOptions,\n    statesAwaitingAttach: NodeAttachedComponentState<any>[],\n    mountOnPreExistingDOM: boolean\n    // boundary: ForgoComponent<any> | undefined\n  ): RenderResult {\n    function renderExistingComponent(\n      insertAt: number,\n      childNodes: NodeListOf<ChildNode>,\n      insertionOptions: SearchableNodeInsertionOptions\n    ): RenderResult {\n      const targetNode = childNodes[insertAt];\n      const state = getExistingForgoState(targetNode);\n      const componentState = state.components[componentIndex];\n\n      // Get rid of unwanted nodes.\n      markNodesForUnloading(\n        childNodes,\n        insertionOptions.currentNodeIndex,\n        insertAt\n      );\n\n      if (\n        lifecycleEmitters.shouldUpdate(\n          componentState.component,\n          forgoComponent.props,\n          componentState.props\n        )\n      ) {\n        // Since we have compatible state already stored,\n        // we'll push the savedComponentState into pending states for later attachment.\n        const updatedComponentState = {\n          ...componentState,\n          props: forgoComponent.props,\n        };\n\n        // Get a new element by calling render on existing component.\n        const newForgoNode =\n          updatedComponentState.component.__internal.registeredMethods.render(\n            forgoComponent.props,\n            updatedComponentState.component\n          );\n\n        const statesToAttach = statesAwaitingAttach.concat(\n          updatedComponentState\n        );\n\n        const previousNode = componentState.component.__internal.element.node;\n\n        const boundary = updatedComponentState.component.__internal\n          .registeredMethods.error\n          ? updatedComponentState.component\n          : undefined;\n\n        const renderResult = withErrorBoundary(\n          forgoComponent.props,\n          statesToAttach,\n          boundary,\n          () => {\n            // Create new node insertion options.\n            const newInsertionOptions: NodeInsertionOptions = {\n              type: \"new-component\",\n              currentNodeIndex: insertionOptions.currentNodeIndex,\n              length: updatedComponentState.nodes.length,\n              parentElement: insertionOptions.parentElement,\n            };\n\n            return renderComponentAndRemoveStaleNodes(\n              newForgoNode,\n              newInsertionOptions,\n              statesToAttach,\n              updatedComponentState,\n              mountOnPreExistingDOM\n            );\n          }\n        );\n\n        lifecycleEmitters.afterRender(\n          updatedComponentState.component,\n          forgoComponent.props,\n          previousNode\n        );\n\n        return renderResult;\n      }\n      // shouldUpdate() returned false\n      else {\n        const indexOfNode = findNodeIndex(\n          insertionOptions.parentElement.childNodes,\n          componentState.component.__internal.element.node\n        );\n\n        return {\n          nodes: sliceNodes(\n            insertionOptions.parentElement.childNodes,\n            indexOfNode,\n            indexOfNode + componentState.nodes.length\n          ),\n          pendingMounts: [],\n        };\n      }\n    }\n\n    function addComponent(): RenderResult {\n      const ctor = forgoComponent.type;\n      const component = assertIsComponent(ctor, ctor(forgoComponent.props));\n      component.__internal.element.componentIndex = componentIndex;\n\n      const boundary = component.__internal.registeredMethods.error\n        ? component\n        : undefined;\n\n      // Create new component state\n      // ... and push it to statesAwaitAttach[]\n      const newComponentState: NodeAttachedComponentState<any> = {\n        key: forgoComponent.key,\n        ctor,\n        component,\n        props: forgoComponent.props,\n        nodes: [],\n        isMounted: false,\n      };\n\n      const statesToAttach = statesAwaitingAttach.concat(newComponentState);\n\n      return withErrorBoundary(\n        forgoComponent.props,\n        statesToAttach,\n        boundary,\n        () => {\n          // Create an element by rendering the component\n          const newForgoElement = component.__internal.registeredMethods.render(\n            forgoComponent.props,\n            component\n          );\n\n          // Create new node insertion options.\n          const newInsertionOptions: NodeInsertionOptions =\n            insertionOptions.type === \"detached\"\n              ? insertionOptions\n              : {\n                  type: \"new-component\",\n                  currentNodeIndex: insertionOptions.currentNodeIndex,\n                  length: mountOnPreExistingDOM ? insertionOptions.length : 0,\n                  parentElement: insertionOptions.parentElement,\n                };\n\n          // Pass it on for rendering...\n          const renderResult = internalRender(\n            newForgoElement,\n            newInsertionOptions,\n            statesToAttach,\n            mountOnPreExistingDOM\n          );\n\n          // In case we rendered an array, set the node to the first node.\n          // We do this because args.element.node would be set to the last node otherwise.\n          newComponentState.nodes = renderResult.nodes;\n          newComponentState.component.__internal.element.node =\n            renderResult.nodes[0];\n\n          // No previousNode since new component. So just args and not\n          // afterRenderArgs.\n          lifecycleEmitters.afterRender(\n            component,\n            forgoComponent.props,\n            undefined\n          );\n\n          return renderResult;\n        }\n      );\n    }\n\n    function withErrorBoundary(\n      props: TProps,\n      statesToAttach: NodeAttachedComponentState<any>[],\n      boundary: Component<any> | undefined,\n      exec: () => RenderResult\n    ): RenderResult {\n      try {\n        return exec();\n      } catch (error) {\n        if (boundary?.__internal.registeredMethods.error) {\n          const newForgoElement = boundary.__internal.registeredMethods.error!(\n            props,\n            error,\n            boundary\n          );\n          return internalRender(\n            newForgoElement,\n            insertionOptions,\n            statesToAttach,\n            mountOnPreExistingDOM\n          );\n        } else {\n          throw error;\n        }\n      }\n    }\n\n    const componentIndex = statesAwaitingAttach.length;\n\n    if (\n      // We need to create a detached node.\n      insertionOptions.type !== \"detached\" &&\n      // We have to find a node to replace.\n      insertionOptions.length &&\n      !mountOnPreExistingDOM\n    ) {\n      const childNodes = insertionOptions.parentElement.childNodes;\n      const searchResult = findReplacementCandidateForComponent(\n        forgoComponent,\n        insertionOptions.parentElement,\n        insertionOptions.currentNodeIndex,\n        insertionOptions.length,\n        statesAwaitingAttach.length\n      );\n\n      if (searchResult.found) {\n        return renderExistingComponent(\n          searchResult.index,\n          childNodes,\n          insertionOptions\n        );\n      }\n    }\n    // No nodes in target node list, or no matching node found.\n    // Nothing to unload.\n    return addComponent();\n  }\n\n  function renderComponentAndRemoveStaleNodes<TProps extends object>(\n    forgoNode: ForgoNode,\n    insertionOptions: SearchableNodeInsertionOptions,\n    statesToAttach: NodeAttachedComponentState<any>[],\n    componentState: NodeAttachedComponentState<TProps>,\n    mountOnPreExistingDOM: boolean\n  ): RenderResult {\n    const totalNodesBeforeRender =\n      insertionOptions.parentElement.childNodes.length;\n\n    // Pass it on for rendering...\n    const renderResult = internalRender(\n      forgoNode,\n      insertionOptions,\n      statesToAttach,\n      mountOnPreExistingDOM\n    );\n\n    const totalNodesAfterRender =\n      insertionOptions.parentElement.childNodes.length;\n\n    const numNodesReused =\n      totalNodesBeforeRender +\n      renderResult.nodes.length -\n      totalNodesAfterRender;\n\n    // Since we have re-rendered, we might need to delete a bunch of nodes from the previous render.\n    // That list begins from currentIndex + num nodes in latest render.\n    // Delete up to deleteFromIndex + componentState.nodes.length - numNodesReused,\n    //  in which componentState.nodes.length is num nodes from previous render.\n    const deleteFromIndex =\n      insertionOptions.currentNodeIndex + renderResult.nodes.length;\n\n    const deletedNodes = markNodesForUnloading(\n      insertionOptions.parentElement.childNodes,\n      deleteFromIndex,\n      deleteFromIndex + componentState.nodes.length - numNodesReused\n    );\n\n    /*\n     * transferredState is the state that's already been remounted on a different node.\n     * Components in transferredState should not be unmounted, since this is already\n     * being tracked on a different node. Hence transferredState needs to be removed\n     * from deletedNodes.\n     */\n\n    const transferredState =\n      renderResult.nodes.length > 0 ? statesToAttach : [];\n\n    // Patch state in deletedNodes to exclude what's been already transferred.\n    for (const deletedNode of deletedNodes) {\n      const state = getForgoState(deletedNode);\n      if (state) {\n        const indexOfFirstIncompatibleState = findIndexOfFirstIncompatibleState(\n          transferredState,\n          state.components\n        );\n        state.components = state.components.slice(\n          indexOfFirstIncompatibleState\n        );\n      }\n    }\n\n    // In case we rendered an array, set the node to the first node.\n    // We do this because args.element.node would be set to the last node otherwise.\n    componentState.nodes = renderResult.nodes;\n    componentState.component.__internal.element.node = renderResult.nodes[0];\n\n    return renderResult;\n  }\n\n  /*\n    Render an array of components. \n    Called when a Component returns an array (or fragment) in its render method.  \n  */\n  function renderArray(\n    forgoNodes: ForgoNode[],\n    insertionOptions: NodeInsertionOptions,\n    statesAwaitingAttach: NodeAttachedComponentState<any>[],\n    mountOnPreExistingDOM: boolean\n  ): RenderResult {\n    const flattenedNodes = flatten(forgoNodes);\n\n    if (insertionOptions.type === \"detached\") {\n      throw new Error(\n        \"Arrays and fragments cannot be rendered at the top level.\"\n      );\n    } else {\n      const renderResults: RenderResult = { nodes: [], pendingMounts: [] };\n\n      let currentNodeIndex = insertionOptions.currentNodeIndex;\n      let numNodes = insertionOptions.length;\n\n      for (const forgoNode of flattenedNodes) {\n        const totalNodesBeforeRender =\n          insertionOptions.parentElement.childNodes.length;\n\n        const newInsertionOptions: SearchableNodeInsertionOptions = {\n          ...insertionOptions,\n          currentNodeIndex,\n          length: numNodes,\n        };\n\n        const renderResult = internalRender(\n          forgoNode,\n          newInsertionOptions,\n          statesAwaitingAttach,\n          mountOnPreExistingDOM\n        );\n\n        renderResults.nodes.push(...renderResult.nodes);\n        renderResults.pendingMounts.push(...renderResult.pendingMounts);\n\n        const totalNodesAfterRender =\n          insertionOptions.parentElement.childNodes.length;\n\n        const numNodesRemoved =\n          totalNodesBeforeRender +\n          renderResult.nodes.length -\n          totalNodesAfterRender;\n\n        currentNodeIndex += renderResult.nodes.length;\n        numNodes -= numNodesRemoved;\n      }\n\n      return renderResults;\n    }\n  }\n\n  /**\n   * This doesn't unmount components attached to these nodes, but moves the node\n   * itself from the DOM to parentNode.__forgo_deletedNodes. We sort of \"mark\"\n   * it for deletion, but it may be resurrected if it's matched by a keyed forgo\n   * node that has been reordered.\n   *\n   * Nodes in between `from` and `to` (not inclusive of `to`) will be marked for\n   * unloading. Use `unloadMarkedNodes()` to actually unload the nodes once\n   * we're sure we don't need to resurrect them.\n   *\n   * We don't want to remove DOM nodes that aren't owned by Forgo. I.e., if the\n   * user grabs a reference to a DOM element and manually adds children under\n   * it, we don't want to remove those children. That'll mess up e.g., charting\n   * libraries.\n   */\n  function markNodesForUnloading(\n    nodes: ArrayLike<ChildNode>,\n    from: number,\n    to: number\n  ): ChildNode[] {\n    const justDeletedNodes: ChildNode[] = [];\n\n    const nodesToRemove = sliceNodes(nodes, from, to);\n    if (nodesToRemove.length) {\n      const parentElement = nodesToRemove[0].parentElement as HTMLElement;\n      const deletedNodes = getDeletedNodes(parentElement);\n      for (const node of nodesToRemove) {\n        // If the consuming application has manually mucked with the DOM don't\n        // remove things it added\n        const state = getForgoState(node);\n        if (!state) continue;\n\n        node.remove();\n        justDeletedNodes.push(node);\n        deletedNodes.push({ node });\n      }\n    }\n\n    return justDeletedNodes;\n  }\n\n  /*\n      Unmount components from nodes.\n      We unmount only after first incompatible state, since compatible states \n      will be reattached to new candidate node.\n    */\n  function unloadMarkedNodes(\n    parentElement: Element,\n    statesAwaitingAttach: NodeAttachedComponentState<any>[]\n  ) {\n    const deletedNodes = getDeletedNodes(parentElement);\n\n    for (const { node } of deletedNodes) {\n      const state = getForgoState(node);\n      if (state) {\n        state.deleted = true;\n        const oldComponentStates = state.components;\n        unmountComponents(statesAwaitingAttach, oldComponentStates);\n      }\n    }\n    clearDeletedNodes(parentElement);\n  }\n\n  /*\n    When states are attached to a new node or when states are reattached, \n    some of the old component states need to go away. The corresponding components \n    will need to be unmounted.\n\n    While rendering, the component gets reused if the ctor is the same. If the \n    ctor is different, the component is discarded. And hence needs to be unmounted.\n    So we check the ctor type in old and new.\n  */\n  function findIndexOfFirstIncompatibleState(\n    newStates: NodeAttachedComponentState<any>[],\n    oldStates: NodeAttachedComponentState<any>[]\n  ): number {\n    let i = 0;\n\n    for (const newState of newStates) {\n      if (oldStates.length > i) {\n        const oldState = oldStates[i];\n        if (oldState.component !== newState.component) {\n          break;\n        }\n        i++;\n      } else {\n        break;\n      }\n    }\n\n    return i;\n  }\n\n  /**\n   * Unmount components above an index. This is going to be passed a stale\n   * state[].\n   *\n   * The `unmount` lifecycle event will be called.\n   */\n  function unmountComponents(\n    statesAwaitingAttach: NodeAttachedComponentState<any>[],\n    oldComponentStates: NodeAttachedComponentState<any>[] | undefined\n  ) {\n    if (!oldComponentStates) return;\n\n    // If the parent has already unmounted, we can skip checks on children.\n    let parentHasUnmounted = false;\n\n    const indexOfFirstIncompatibleState = findIndexOfFirstIncompatibleState(\n      statesAwaitingAttach,\n      oldComponentStates\n    );\n\n    for (\n      let i = indexOfFirstIncompatibleState;\n      i < oldComponentStates.length;\n      i++\n    ) {\n      const state = oldComponentStates[i];\n      const component = state.component;\n      // Render if:\n      //  - parent has already unmounted\n      //  - OR for all nodes:\n      //  -   node is disconnected\n      //  -   OR node connected to a different component\n      if (\n        parentHasUnmounted ||\n        state.nodes.every((x) => {\n          if (!x.isConnected) {\n            return true;\n          } else {\n            const stateOnCurrentNode = getExistingForgoState(x);\n            return (\n              stateOnCurrentNode.components[i] === undefined ||\n              stateOnCurrentNode.components[i].component !== state.component\n            );\n          }\n        })\n      ) {\n        if (!component.__internal.unmounted) {\n          component.__internal.unmounted = true;\n          lifecycleEmitters.unmount(component, state.props);\n        }\n        parentHasUnmounted = true;\n      }\n    }\n  }\n\n  /**\n   * Mount components above an index. This is going to be passed the new\n   * state[].\n   */\n  function mountComponents(\n    statesAwaitingAttach: NodeAttachedComponentState<any>[],\n    oldComponentStates: NodeAttachedComponentState<any>[] | undefined\n  ) {\n    const indexOfFirstIncompatibleState = oldComponentStates\n      ? findIndexOfFirstIncompatibleState(\n          statesAwaitingAttach,\n          oldComponentStates\n        )\n      : 0;\n\n    for (\n      let i = indexOfFirstIncompatibleState;\n      i < statesAwaitingAttach.length;\n      i++\n    ) {\n      const state = statesAwaitingAttach[i];\n      // This function is called in every syncStateAndProps() call, so many of\n      // the calls will be for already-mounted components. Only fire the mount\n      // lifecycle events when appropriate.\n      if (!state.isMounted) {\n        state.isMounted = true;\n        // Set this before calling the lifecycle handlers to fix #70\n        lifecycleEmitters.mount(state.component, state.props);\n      }\n    }\n  }\n\n  type CandidateSearchResult =\n    | {\n        found: false;\n      }\n    | { found: true; index: number };\n\n  /**\n   * When we try to find replacement candidates for DOM nodes,\n   * we try to:\n   *   a) match by the key\n   *   b) match by the tagname\n   */\n  function findReplacementCandidateForElement<\n    TProps extends ForgoDOMElementProps\n  >(\n    forgoElement: ForgoDOMElement<TProps>,\n    parentElement: Element,\n    searchFrom: number,\n    length: number\n  ): CandidateSearchResult {\n    const nodes = parentElement.childNodes;\n    for (let i = searchFrom; i < searchFrom + length; i++) {\n      const node = nodes[i] as ChildNode;\n      if (nodeIsElement(node)) {\n        const stateOnNode = getForgoState(node);\n        // If the user stuffs random elements into the DOM manually, we don't\n        // want to treat them as replacement candidates - they should be left\n        // alone.\n        if (!stateOnNode) continue;\n\n        if (\n          forgoElement.key !== undefined &&\n          stateOnNode?.key === forgoElement.key\n        ) {\n          return { found: true, index: i };\n        } else {\n          // If the candidate has a key defined,\n          //  we don't match it with an unkeyed forgo element\n          if (\n            node.tagName.toLowerCase() === forgoElement.type &&\n            (stateOnNode === undefined || stateOnNode.key === undefined)\n          ) {\n            return { found: true, index: i };\n          }\n        }\n      }\n    }\n    // Let's check deleted nodes as well.\n    if (forgoElement.key !== undefined) {\n      const deletedNodes = getDeletedNodes(parentElement);\n      for (let i = 0; i < deletedNodes.length; i++) {\n        const { node } = deletedNodes[i];\n        const stateOnNode = getForgoState(node);\n        if (stateOnNode?.key === forgoElement.key) {\n          // Remove it from deletedNodes.\n          deletedNodes.splice(i, 1);\n          // Append it to the beginning of the node list.\n          const firstNodeInSearchList = nodes[searchFrom];\n          if (!isNullOrUndefined(firstNodeInSearchList)) {\n            parentElement.insertBefore(node, firstNodeInSearchList);\n          } else {\n            parentElement.appendChild(node);\n          }\n          return { found: true, index: searchFrom };\n        }\n      }\n    }\n    return { found: false };\n  }\n\n  /**\n   * When we try to find replacement candidates for Components,\n   * we try to:\n   *   a) match by the key\n   *   b) match by the component constructor\n   */\n  function findReplacementCandidateForComponent<\n    TProps extends ForgoDOMElementProps\n  >(\n    forgoElement: ForgoComponentElement<TProps>,\n    parentElement: Element,\n    searchFrom: number,\n    length: number,\n    componentIndex: number\n  ): CandidateSearchResult {\n    const nodes = parentElement.childNodes;\n    for (let i = searchFrom; i < searchFrom + length; i++) {\n      const node = nodes[i] as ChildNode;\n      const stateOnNode = getForgoState(node);\n      if (stateOnNode && stateOnNode.components.length > componentIndex) {\n        if (forgoElement.key !== undefined) {\n          if (\n            stateOnNode.components[componentIndex].ctor === forgoElement.type &&\n            stateOnNode.components[componentIndex].key === forgoElement.key\n          ) {\n            return { found: true, index: i };\n          }\n        } else {\n          if (\n            stateOnNode.components[componentIndex].ctor === forgoElement.type\n          ) {\n            return { found: true, index: i };\n          }\n        }\n      }\n    }\n\n    // Check if a keyed component is mounted on this node.\n    function nodeBelongsToKeyedComponent(\n      node: ChildNode,\n      forgoElement: ForgoComponentElement<TProps>,\n      componentIndex: number\n    ) {\n      const stateOnNode = getForgoState(node);\n      if (stateOnNode && stateOnNode.components.length > componentIndex) {\n        if (\n          stateOnNode.components[componentIndex].ctor === forgoElement.type &&\n          stateOnNode.components[componentIndex].key === forgoElement.key\n        ) {\n          return true;\n        }\n      }\n      return false;\n    }\n\n    // Let's check deleted nodes as well.\n    if (forgoElement.key !== undefined) {\n      const deletedNodes = getDeletedNodes(parentElement);\n      for (let i = 0; i < deletedNodes.length; i++) {\n        const { node: deletedNode } = deletedNodes[i];\n        if (\n          nodeBelongsToKeyedComponent(deletedNode, forgoElement, componentIndex)\n        ) {\n          const nodesToResurrect: ChildNode[] = [deletedNode];\n          // Found a match!\n          // Collect all consecutive matching nodes.\n          for (let j = i + 1; j < deletedNodes.length; j++) {\n            const { node: subsequentNode } = deletedNodes[j];\n            if (\n              nodeBelongsToKeyedComponent(\n                subsequentNode,\n                forgoElement,\n                componentIndex\n              )\n            ) {\n              nodesToResurrect.push(subsequentNode);\n            }\n          }\n          // Remove them from deletedNodes.\n          deletedNodes.splice(i, nodesToResurrect.length);\n\n          // Append resurrected nodes to the beginning of the node list.\n          const insertBeforeNode = nodes[searchFrom];\n\n          if (!isNullOrUndefined(insertBeforeNode)) {\n            for (const node of nodesToResurrect) {\n              parentElement.insertBefore(node, insertBeforeNode);\n            }\n          } else {\n            for (const node of nodesToResurrect) {\n              parentElement.appendChild(node);\n            }\n          }\n\n          return { found: true, index: searchFrom };\n        }\n      }\n    }\n    return { found: false };\n  }\n\n  /**\n   * Attach props from the forgoElement onto the DOM node. We also need to attach\n   * states from statesAwaitingAttach\n   */\n  function syncAttrsAndState(\n    forgoNode: ForgoNode,\n    node: ChildNode,\n    isNewNode: boolean,\n    statesAwaitingAttach: NodeAttachedComponentState<any>[]\n  ) {\n    // We have to inject node into the args object.\n    // components are already holding a reference to the args object.\n    // They don't know yet that args.element.node is undefined.\n    if (statesAwaitingAttach.length > 0) {\n      statesAwaitingAttach[\n        statesAwaitingAttach.length - 1\n      ].component.__internal.element.node = node;\n    }\n\n    if (isForgoElement(forgoNode)) {\n      const existingState = getForgoState(node);\n\n      // Remove props which don't exist\n      if (existingState && existingState.props) {\n        for (const key in existingState.props) {\n          if (!(key in forgoNode.props)) {\n            if (key !== \"children\" && key !== \"xmlns\") {\n              if (\n                node.nodeType === TEXT_NODE_TYPE ||\n                node.nodeType === COMMENT_NODE_TYPE\n              ) {\n                delete (node as any)[key];\n              } else if (node instanceof env.__internal.HTMLElement) {\n                if (key in node) {\n                  delete (node as any)[key];\n                } else {\n                  (node as HTMLElement).removeAttribute(key);\n                }\n              } else {\n                (node as Element).removeAttribute(key);\n              }\n            }\n          }\n        }\n      } else {\n        // A new node which doesn't have forgoState is SSR.\n        // We have to manually extinguish props\n        if (!isNewNode && nodeIsElement(node)) {\n          if (node.hasAttributes()) {\n            const attributes = Array.from(node.attributes);\n            for (const attr of attributes) {\n              const key = attr.name;\n              if (!(key in forgoNode.props)) {\n                node.removeAttribute(key);\n              }\n            }\n          }\n        }\n      }\n\n      // TODO: What preact does to figure out attr vs prop\n      //  - do a (key in element) check.\n      const entries = Object.entries(forgoNode.props);\n      for (const [key, value] of entries) {\n        if (suppressedAttributes.includes(key)) continue;\n\n        // The browser will sometimes perform side effects if an attribute is\n        // set, even if its value hasn't changed, so only update attrs if\n        // necessary. See issue #32.\n        if (existingState?.props?.[key] !== value) {\n          if (key !== \"children\" && key !== \"xmlns\") {\n            if (\n              node.nodeType === TEXT_NODE_TYPE ||\n              node.nodeType === COMMENT_NODE_TYPE\n            ) {\n              (node as any)[key] = value;\n            } else if (node instanceof env.__internal.HTMLElement) {\n              if (key === \"style\") {\n                // Optimization: many times in CSS to JS, style objects are re-used.\n                // If they're the same, skip the expensive styleToString() call.\n                if (\n                  existingState === undefined ||\n                  existingState.style === undefined ||\n                  existingState.style !== forgoNode.props.style\n                ) {\n                  const stringOfCSS = styleToString(forgoNode.props.style);\n                  if ((node as HTMLElement).style.cssText !== stringOfCSS) {\n                    (node as HTMLElement).style.cssText = stringOfCSS;\n                  }\n                }\n              }\n              // This optimization is copied from preact.\n              else if (key === \"onblur\") {\n                (node as any)[key] = handlerDisabledOnNodeDelete(node, value);\n              } else if (key in node) {\n                (node as any)[key] = value;\n              } else {\n                (node as any).setAttribute(key, value);\n              }\n            } else {\n              if (typeof value === \"string\") {\n                (node as Element).setAttribute(key, value);\n              } else {\n                (node as any)[key] = value;\n              }\n            }\n          }\n        }\n      }\n\n      // Now attach the internal forgo state.\n      const state: NodeAttachedState = {\n        key: forgoNode.key,\n        props: forgoNode.props,\n        components: statesAwaitingAttach,\n      };\n\n      setForgoState(node, state);\n    } else {\n      // Now attach the internal forgo state.\n      const state: NodeAttachedState = {\n        components: statesAwaitingAttach,\n      };\n\n      setForgoState(node, state);\n    }\n  }\n\n  /*\n    Mount will render the DOM as a child of the specified container element.\n  */\n  function mount(\n    forgoNode: ForgoNode,\n    container: Element | string | null\n  ): RenderResult {\n    const parentElement = (\n      isString(container) ? env.document.querySelector(container) : container\n    ) as Element;\n\n    if (isNullOrUndefined(parentElement)) {\n      throw new Error(\n        `The mount() function was called on a non-element (${\n          typeof container === \"string\" ? container : container?.tagName\n        }).`\n      );\n    }\n    if (parentElement.nodeType !== ELEMENT_NODE_TYPE) {\n      throw new Error(\n        \"The container argument to the mount() function should be an HTML element.\"\n      );\n    }\n\n    const mountOnPreExistingDOM = parentElement.childNodes.length > 0;\n    const result = internalRender(\n      forgoNode,\n      {\n        type: \"new-component\",\n        currentNodeIndex: 0,\n        length: parentElement.childNodes.length,\n        parentElement,\n      },\n      [],\n      mountOnPreExistingDOM\n    );\n\n    // Remove excess nodes.\n    // This happens when there are pre-existing nodes.\n    if (result.nodes.length < parentElement.childNodes.length) {\n      const nodesToRemove = sliceNodes(\n        parentElement.childNodes,\n        result.nodes.length,\n        parentElement.childNodes.length\n      );\n      for (const node of nodesToRemove) {\n        node.remove();\n      }\n    }\n\n    return result;\n  }\n\n  function unmount(container: Element | string | null) {\n    const parentElement = (\n      isString(container) ? env.document.querySelector(container) : container\n    ) as Element;\n\n    if (isNullOrUndefined(parentElement)) {\n      throw new Error(\n        `The unmount() function was called on a non-element (${\n          typeof container === \"string\" ? container : container?.tagName\n        }).`\n      );\n    }\n    if (parentElement.nodeType !== ELEMENT_NODE_TYPE) {\n      throw new Error(\n        \"The container argument to the unmount() function should be an HTML element.\"\n      );\n    }\n\n    markNodesForUnloading(\n      parentElement.childNodes,\n      0,\n      parentElement.childNodes.length\n    );\n    unloadMarkedNodes(parentElement, []);\n  }\n\n  /*\n    This render function returns the rendered dom node.\n    forgoNode is the node to render.\n  */\n  function render(forgoNode: ForgoNode): {\n    node: ChildNode;\n    nodes: ChildNode[];\n  } {\n    const renderResult = internalRender(\n      forgoNode,\n      {\n        type: \"detached\",\n      },\n      [],\n      false\n    );\n    return { node: renderResult.nodes[0], nodes: renderResult.nodes };\n  }\n\n  /**\n   * Code inside a component will call rerender whenever it wants to rerender.\n   * The following function is what they'll need to call.\n\n   * Given only a DOM element, how do we know what component to render? We'll\n   * fetch all that information from the state information stored on the\n   * element.\n\n   * This is attached to a node inside a NodeAttachedState structure.\n\n   * @param forceUnmount Allows a user to explicitly tear down a Forgo app from\n      outside the framework\n   */\n  function rerender(\n    element: ForgoElementArg | undefined,\n    props?: any\n  ): RenderResult {\n    if (!element?.node) {\n      throw new Error(`Missing node information in rerender() argument.`);\n    }\n\n    const parentElement = element.node.parentElement;\n    if (parentElement !== null) {\n      const state = getExistingForgoState(element.node);\n\n      const originalComponentState = state.components[element.componentIndex];\n\n      const effectiveProps = props ?? originalComponentState.props;\n\n      if (\n        !lifecycleEmitters.shouldUpdate(\n          originalComponentState.component,\n          effectiveProps,\n          originalComponentState.props\n        )\n      ) {\n        const indexOfNode = findNodeIndex(\n          parentElement.childNodes,\n          element.node\n        );\n\n        return {\n          nodes: sliceNodes(\n            parentElement.childNodes,\n            indexOfNode,\n            indexOfNode + originalComponentState.nodes.length\n          ),\n          pendingMounts: [],\n        };\n      }\n\n      const componentStateWithUpdatedProps = {\n        ...originalComponentState,\n        props: effectiveProps,\n      };\n\n      const parentStates = state.components.slice(0, element.componentIndex);\n\n      const statesToAttach = parentStates.concat(\n        componentStateWithUpdatedProps\n      );\n\n      const previousNode =\n        originalComponentState.component.__internal.element.node;\n\n      const forgoNode =\n        originalComponentState.component.__internal.registeredMethods.render(\n          effectiveProps,\n          originalComponentState.component\n        );\n\n      const nodeIndex = findNodeIndex(parentElement.childNodes, element.node);\n\n      const insertionOptions: SearchableNodeInsertionOptions = {\n        type: \"new-component\",\n        currentNodeIndex: nodeIndex,\n        length: originalComponentState.nodes.length,\n        parentElement,\n      };\n\n      const renderResult = renderComponentAndRemoveStaleNodes(\n        forgoNode,\n        insertionOptions,\n        statesToAttach,\n        componentStateWithUpdatedProps,\n        false\n      );\n\n      // We have to propagate node changes up the component Tree.\n      // Reason 1:\n      //  Imaging Parent rendering Child1 & Child2\n      //  Child1 renders [div1, div2], and Child2 renders [div3, div4].\n      //  When Child1's rerender is called, it might return [p1] instead of [div1, div2]\n      //  Now, Parent's node list (ie state.nodes) must be refreshed to [p1, div3, div4] from [div1, div2, div3, div4]\n      // Reason 2:\n      //  If Child2 was rerendered (instead of Child1), attachProps() will incorrectly fixup parentState.element.node to div3, then to div4.\n      //  That's just how attachProps() works. We need to ressign parentState.element.node to p1.\n      for (let i = 0; i < parentStates.length; i++) {\n        const parentState = parentStates[i];\n\n        const indexOfOriginalRootNode = parentState.nodes.findIndex(\n          (x) => x === originalComponentState.nodes[0]\n        );\n\n        // Let's recreate the node list.\n        parentState.nodes = parentState.nodes\n          // 1. all the nodes before first node associated with rendered component.\n          .slice(0, indexOfOriginalRootNode)\n          // 2. newly created nodes.\n          .concat(renderResult.nodes)\n          // 3. nodes after last node associated with rendered component.\n          .concat(\n            parentState.nodes.slice(\n              indexOfOriginalRootNode + originalComponentState.nodes.length\n            )\n          );\n\n        // Fix up the root node for parent.\n        if (parentState.nodes.length > 0) {\n          // The root node might have changed, so fix it up just in case.\n          parentState.component.__internal.element.node = parentState.nodes[0];\n        }\n      }\n\n      // Unload marked nodes.\n      unloadMarkedNodes(\n        parentElement,\n        renderResult.nodes.length > 0 ? statesToAttach : []\n      );\n\n      // Unmount rendered component itself if all nodes are gone.\n      // if (renderResult.nodes.length === 0) {\n      //   unmountComponents([newComponentState], 0);\n      // }\n\n      // Run afterRender() if defined.\n      lifecycleEmitters.afterRender(\n        originalComponentState.component,\n        effectiveProps,\n        previousNode\n      );\n\n      return renderResult;\n    } else {\n      return { nodes: [], pendingMounts: [] };\n    }\n  }\n\n  function createElement(\n    forgoElement: ForgoDOMElement<{ is?: string; xmlns?: string }>,\n    parentElement: Element | undefined\n  ) {\n    const namespaceURI =\n      forgoElement.props.xmlns !== undefined\n        ? (forgoElement.props.xmlns as string)\n        : forgoElement.type === \"svg\"\n        ? SVG_NAMESPACE\n        : parentElement\n        ? parentElement.namespaceURI\n        : null;\n\n    if (forgoElement.props.is !== undefined) {\n      return namespaceURI !== null\n        ? env.document.createElementNS(namespaceURI, forgoElement.type, {\n            is: forgoElement.props.is,\n          })\n        : env.document.createElement(forgoElement.type, {\n            is: forgoElement.props.is,\n          });\n    } else {\n      return namespaceURI !== null\n        ? env.document.createElementNS(namespaceURI, forgoElement.type)\n        : env.document.createElement(forgoElement.type);\n    }\n  }\n\n  return {\n    mount,\n    unmount,\n    render,\n    rerender,\n  };\n}\n\nconst windowObject = globalThis !== undefined ? globalThis : window;\n\nlet forgoInstance = createForgoInstance({\n  window: windowObject,\n  document: windowObject.document,\n});\n\nexport function setCustomEnv(customEnv: any) {\n  forgoInstance = createForgoInstance(customEnv);\n}\n\n/**\n * Attach a new Forgo application to a DOM element\n */\nexport function mount(\n  forgoNode: ForgoNode,\n  container: Element | string | null\n): RenderResult {\n  return forgoInstance.mount(forgoNode, container);\n}\n\n/**\n * Unmount a Forgo application from outside.\n * @param container The root element that the Forgo app was mounted onto\n */\nexport function unmount(container: Element | string | null): void {\n  return forgoInstance.unmount(container);\n}\n\nexport function render(forgoNode: ForgoNode): {\n  node: ChildNode;\n  nodes: ChildNode[];\n} {\n  return forgoInstance.render(forgoNode);\n}\n\nexport function rerender(\n  element: ForgoElementArg | undefined,\n  props?: any\n): RenderResult {\n  return forgoInstance.rerender(element, props);\n}\n\n/*\n  This recursively flattens an array or a Fragment.\n  Fragments are treated as arrays, with the children prop being array items.\n*/\nfunction flatten(itemOrItems: ForgoNode | ForgoNode[]): ForgoNode[] {\n  function recurse(\n    itemOrItems: ForgoNode | ForgoNode[],\n    ret: ForgoNode[] = []\n  ) {\n    const items = Array.isArray(itemOrItems)\n      ? itemOrItems\n      : isForgoFragment(itemOrItems)\n      ? Array.isArray(itemOrItems.props.children)\n        ? itemOrItems.props.children\n        : !isNullOrUndefined(itemOrItems.props.children)\n        ? [itemOrItems.props.children]\n        : []\n      : [itemOrItems];\n    for (const entry of items) {\n      if (Array.isArray(entry) || isForgoFragment(entry)) {\n        recurse(entry, ret);\n      } else {\n        ret.push(entry);\n      }\n    }\n    return ret;\n  }\n\n  return recurse(itemOrItems, []);\n}\n\n/**\n * ForgoNodes can be primitive types. Convert all primitive types to their\n * string representation.\n */\nfunction stringOfNode(node: ForgoNonEmptyPrimitiveNode): string {\n  return node.toString();\n}\n\n/**\n * Get Node Types\n */\nfunction isForgoElement(forgoNode: ForgoNode): forgoNode is ForgoElement<any> {\n  return (\n    forgoNode !== undefined &&\n    forgoNode !== null &&\n    (forgoNode as any).__is_forgo_element__ === true\n  );\n}\n\nfunction isForgoDOMElement(node: ForgoNode): node is ForgoDOMElement<any> {\n  return isForgoElement(node) && typeof node.type === \"string\";\n}\n\nfunction isForgoFragment(node: ForgoNode): node is ForgoFragment {\n  return node !== undefined && node !== null && (node as any).type === Fragment;\n}\n\n/*\n  Get the state (NodeAttachedState) saved into an element.\n*/\nexport function getForgoState(node: ChildNode): NodeAttachedState | undefined {\n  return node.__forgo;\n}\n\n/*\n  Same as above, but throws if undefined. (Caller must make sure.)\n*/\nfunction getExistingForgoState(node: ChildNode): NodeAttachedState {\n  if (node.__forgo) {\n    return node.__forgo;\n  } else {\n    throw new Error(\"Missing forgo state on node.\");\n  }\n}\n\n/*\n  Sets the state (NodeAttachedState) on an element.\n*/\nexport function setForgoState(node: ChildNode, state: NodeAttachedState): void {\n  node.__forgo = state;\n}\n\n/*\n  We maintain a list of deleted childNodes on an element.\n  In case we need to resurrect it - on account of a subsequent out-of-order key referring that node.\n*/\nfunction getDeletedNodes(element: Element): DeletedNode[] {\n  if (!element.__forgo_deletedNodes) {\n    element.__forgo_deletedNodes = [];\n  }\n  return element.__forgo_deletedNodes;\n}\n\nfunction clearDeletedNodes(element: Element) {\n  if (element.__forgo_deletedNodes) {\n    element.__forgo_deletedNodes = [];\n  }\n}\n\n/**\n * We bridge the old component syntax to the new syntax until our next breaking release\n */\nexport type ForgoLegacyComponent<TProps extends object> = {\n  render: (\n    props: TProps & ForgoElementBaseProps,\n    args: ForgoRenderArgs\n  ) => ForgoNode | ForgoNode[];\n  afterRender?: (\n    props: TProps & ForgoElementBaseProps,\n    args: ForgoAfterRenderArgs\n  ) => void;\n  error?: (\n    props: TProps & ForgoElementBaseProps,\n    args: ForgoErrorArgs\n  ) => ForgoNode;\n  mount?: (\n    props: TProps & ForgoElementBaseProps,\n    args: ForgoRenderArgs\n  ) => void;\n  unmount?: (\n    props: TProps & ForgoElementBaseProps,\n    args: ForgoRenderArgs\n  ) => void;\n  shouldUpdate?: (\n    newProps: TProps & ForgoElementBaseProps,\n    oldProps: TProps & ForgoElementBaseProps\n  ) => boolean;\n  __forgo?: { unmounted?: boolean };\n};\n\nexport type ForgoRenderArgs = {\n  element: ForgoElementArg;\n  update: (props?: any) => RenderResult;\n};\nexport type ForgoAfterRenderArgs = ForgoRenderArgs & {\n  previousNode?: ChildNode;\n};\nexport type ForgoErrorArgs = ForgoRenderArgs & {\n  error: any;\n};\n\n// We export this so forgo-state & friends can publish non-breaking\n// compatibility releases\nexport const legacyComponentSyntaxCompat = <TProps extends object>(\n  legacyComponent: ForgoLegacyComponent<TProps>\n): Component<TProps> => {\n  const mkRenderArgs = (component: Component<TProps>): ForgoRenderArgs => ({\n    get element() {\n      return component.__internal.element;\n    },\n    update(props) {\n      return component.update(props as unknown as TProps);\n    },\n  });\n\n  const componentBody: ForgoComponentMethods<TProps> = {\n    render(props, component) {\n      return legacyComponent.render(props, mkRenderArgs(component));\n    },\n  };\n  if (legacyComponent.error) {\n    componentBody.error = (props, error) => {\n      return legacyComponent.error!(\n        props,\n        Object.assign(mkRenderArgs(component), { error })\n      );\n    };\n  }\n  const component = new Component<TProps>({\n    ...componentBody,\n  });\n  if (legacyComponent.mount) {\n    component.mount((props) => {\n      legacyComponent.mount!(props, mkRenderArgs(component));\n    });\n  }\n  if (legacyComponent.unmount) {\n    component.unmount((props) => {\n      legacyComponent.unmount!(props, mkRenderArgs(component));\n    });\n  }\n  if (legacyComponent.afterRender) {\n    component.afterRender((props, previousNode) => {\n      legacyComponent.afterRender!(\n        props,\n        Object.assign(mkRenderArgs(component), { previousNode })\n      );\n    });\n  }\n  if (legacyComponent.shouldUpdate) {\n    component.shouldUpdate((newProps, oldProps) => {\n      return legacyComponent.shouldUpdate!(newProps, oldProps);\n    });\n  }\n  return component;\n};\n\n/*\n  Throw if component is a non-component\n*/\nfunction assertIsComponent<TProps extends object>(\n  ctor: ForgoNewComponentCtor<TProps> | ForgoSimpleComponentCtor<TProps>,\n  component: Component<TProps> | ForgoLegacyComponent<TProps>\n): Component<TProps> {\n  if (!(component instanceof Component) && Reflect.has(component, \"render\")) {\n    return legacyComponentSyntaxCompat(component);\n  }\n\n  if (!(component instanceof Component)) {\n    throw new Error(\n      `${\n        ctor.name || \"Unnamed\"\n      } component constructor must return an instance of the Component class`\n    );\n  }\n\n  return component;\n}\n\nfunction isNullOrUndefined<T>(\n  value: T | null | undefined\n): value is null | undefined {\n  return value === null || value === undefined;\n}\n\nfunction isString(val: unknown): val is string {\n  return typeof val === \"string\";\n}\n\nfunction nodeIsElement(node: ChildNode): node is Element {\n  return node.nodeType === ELEMENT_NODE_TYPE;\n}\n\n// Thanks Artem Bochkarev\nfunction styleToString(style: any): string {\n  if (typeof style === \"string\") {\n    return style;\n  } else if (style === undefined || style === null) {\n    return \"\";\n  } else {\n    return Object.keys(style).reduce(\n      (acc, key) =>\n        acc +\n        key\n          .split(/(?=[A-Z])/)\n          .join(\"-\")\n          .toLowerCase() +\n        \":\" +\n        style[key] +\n        \";\",\n      \"\"\n    );\n  }\n}\n\n/**\n * node.childNodes is some funky data structure that's not really not an array,\n * so we can't just slice it like normal\n */\nfunction sliceNodes(\n  nodes: ArrayLike<ChildNode>,\n  from: number,\n  to: number\n): ChildNode[] {\n  return Array.from(nodes).slice(from, to);\n}\n\n/**\n * node.childNodes is some funky data structure that's not really not an array,\n * so we can't just search for the value like normal\n */\nfunction findNodeIndex(\n  nodes: ArrayLike<ChildNode>,\n  element: ChildNode | undefined\n): number {\n  if (!element) return -1;\n  return Array.from(nodes).indexOf(element);\n}\n\n/* JSX Types */\n/*\n  JSX typings expect a JSX namespace to be in scope for the forgo module (if a\n  using a jsxFactory like forgo.createElement), or attached to the naked factory\n  function (if using a jsxFactory like createElement).\n\n  See: https://www.typescriptlang.org/docs/handbook/jsx.html#intrinsic-elements\n  Also: https://dev.to/ferdaber/typescript-and-jsx-part-ii---what-can-create-jsx-22h6\n  Also: https://www.innoq.com/en/blog/type-checking-tsx/\n\n  Note that importing a module turns it into a namespace on this side of the\n  import, so it doesn't need to be declared as a namespace inside jsxTypes.ts.\n  However, attempting to declare it that way causes no end of headaches either\n  when trying to reexport it here, or reexport it from a createElement\n  namespace. Some errors arise at comple or build time, and some are only\n  visible when a project attempts to consume forgo.\n*/\n// This covers a consuming project using the forgo.createElement jsxFactory\nexport * as JSX from \"./jsxTypes.js\";\n\n// If jsxTypes is imported using named imports, esbuild doesn't know how to\n// erase the imports and gets pset that \"JSX\" isn't an actual literal value\n// inside the jsxTypes.ts module. We have to import as a different name than the\n// export within createElement because I can't find a way to export a namespace\n// within a namespace without using import aliases.\nimport * as JSXTypes from \"./jsxTypes.js\";\n// The createElement namespace exists so that users can set their TypeScript\n// jsxFactory to createElement instead of forgo.createElement.// eslint-disable-next-line @typescript-eslint/no-namespace\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace createElement {\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  export import JSX = JSXTypes;\n}\n"
  },
  {
    "path": "src/jsxTypes.ts",
    "content": "import { ForgoDOMElementProps } from \".\";\n\n/* JSX Definitions */\ntype Defaultize<Props, Defaults> =\n  // Distribute over unions\n  Props extends any // Make any properties included in Default optional\n    ? Partial<Pick<Props, Extract<keyof Props, keyof Defaults>>> &\n        // Include the remaining properties from Props\n        Pick<Props, Exclude<keyof Props, keyof Defaults>>\n    : never;\n\nexport type LibraryManagedAttributes<Component, Props> = Component extends {\n  defaultProps: infer Defaults;\n}\n  ? Defaultize<Props, Defaults>\n  : Props;\n\nexport interface IntrinsicAttributes {\n  key?: any;\n}\n\nexport interface ElementAttributesProperty {\n  props: any;\n}\n\nexport interface ElementChildrenAttribute {\n  children: any;\n}\n\nexport type DOMCSSProperties = {\n  [key in keyof Omit<\n    CSSStyleDeclaration,\n    | \"item\"\n    | \"setProperty\"\n    | \"removeProperty\"\n    | \"getPropertyValue\"\n    | \"getPropertyPriority\"\n  >]?: string | number | null | undefined;\n};\nexport type AllCSSProperties = {\n  [key: string]: string | number | null | undefined;\n};\nexport interface CSSProperties extends AllCSSProperties, DOMCSSProperties {\n  cssText?: string | null;\n}\n\nexport interface SVGAttributes<Target extends EventTarget = SVGElement>\n  extends HTMLAttributes<Target> {\n  accentHeight?: number | string;\n  accumulate?: \"none\" | \"sum\";\n  additive?: \"replace\" | \"sum\";\n  alignmentBaseline?:\n    | \"auto\"\n    | \"baseline\"\n    | \"before-edge\"\n    | \"text-before-edge\"\n    | \"middle\"\n    | \"central\"\n    | \"after-edge\"\n    | \"text-after-edge\"\n    | \"ideographic\"\n    | \"alphabetic\"\n    | \"hanging\"\n    | \"mathematical\"\n    | \"inherit\";\n  allowReorder?: \"no\" | \"yes\";\n  alphabetic?: number | string;\n  amplitude?: number | string;\n  arabicForm?: \"initial\" | \"medial\" | \"terminal\" | \"isolated\";\n  ascent?: number | string;\n  attributeName?: string;\n  attributeType?: string;\n  autoReverse?: number | string;\n  azimuth?: number | string;\n  baseFrequency?: number | string;\n  baselineShift?: number | string;\n  baseProfile?: number | string;\n  bbox?: number | string;\n  begin?: number | string;\n  bias?: number | string;\n  by?: number | string;\n  calcMode?: number | string;\n  capHeight?: number | string;\n  clip?: number | string;\n  clipPath?: string;\n  clipPathUnits?: number | string;\n  clipRule?: number | string;\n  colorInterpolation?: number | string;\n  colorInterpolationFilters?: \"auto\" | \"sRGB\" | \"linearRGB\" | \"inherit\";\n  colorProfile?: number | string;\n  colorRendering?: number | string;\n  contentScriptType?: number | string;\n  contentStyleType?: number | string;\n  cursor?: number | string;\n  cx?: number | string;\n  cy?: number | string;\n  d?: string;\n  decelerate?: number | string;\n  descent?: number | string;\n  diffuseConstant?: number | string;\n  direction?: number | string;\n  display?: number | string;\n  divisor?: number | string;\n  dominantBaseline?: number | string;\n  dur?: number | string;\n  dx?: number | string;\n  dy?: number | string;\n  edgeMode?: number | string;\n  elevation?: number | string;\n  enableBackground?: number | string;\n  end?: number | string;\n  exponent?: number | string;\n  externalResourcesRequired?: number | string;\n  fill?: string;\n  fillOpacity?: number | string;\n  fillRule?: \"nonzero\" | \"evenodd\" | \"inherit\";\n  filter?: string;\n  filterRes?: number | string;\n  filterUnits?: number | string;\n  floodColor?: number | string;\n  floodOpacity?: number | string;\n  focusable?: number | string;\n  fontFamily?: string;\n  fontSize?: number | string;\n  fontSizeAdjust?: number | string;\n  fontStretch?: number | string;\n  fontStyle?: number | string;\n  fontVariant?: number | string;\n  fontWeight?: number | string;\n  format?: number | string;\n  from?: number | string;\n  fx?: number | string;\n  fy?: number | string;\n  g1?: number | string;\n  g2?: number | string;\n  glyphName?: number | string;\n  glyphOrientationHorizontal?: number | string;\n  glyphOrientationVertical?: number | string;\n  glyphRef?: number | string;\n  gradientTransform?: string;\n  gradientUnits?: string;\n  hanging?: number | string;\n  horizAdvX?: number | string;\n  horizOriginX?: number | string;\n  ideographic?: number | string;\n  imageRendering?: number | string;\n  in2?: number | string;\n  in?: string;\n  intercept?: number | string;\n  k1?: number | string;\n  k2?: number | string;\n  k3?: number | string;\n  k4?: number | string;\n  k?: number | string;\n  kernelMatrix?: number | string;\n  kernelUnitLength?: number | string;\n  kerning?: number | string;\n  keyPoints?: number | string;\n  keySplines?: number | string;\n  keyTimes?: number | string;\n  lengthAdjust?: number | string;\n  letterSpacing?: number | string;\n  lightingColor?: number | string;\n  limitingConeAngle?: number | string;\n  local?: number | string;\n  markerEnd?: string;\n  markerHeight?: number | string;\n  markerMid?: string;\n  markerStart?: string;\n  markerUnits?: number | string;\n  markerWidth?: number | string;\n  mask?: string;\n  maskContentUnits?: number | string;\n  maskUnits?: number | string;\n  mathematical?: number | string;\n  mode?: number | string;\n  numOctaves?: number | string;\n  offset?: number | string;\n  opacity?: number | string;\n  operator?: number | string;\n  order?: number | string;\n  orient?: number | string;\n  orientation?: number | string;\n  origin?: number | string;\n  overflow?: number | string;\n  overlinePosition?: number | string;\n  overlineThickness?: number | string;\n  paintOrder?: number | string;\n  panose1?: number | string;\n  pathLength?: number | string;\n  patternContentUnits?: string;\n  patternTransform?: number | string;\n  patternUnits?: string;\n  pointerEvents?: number | string;\n  points?: string;\n  pointsAtX?: number | string;\n  pointsAtY?: number | string;\n  pointsAtZ?: number | string;\n  preserveAlpha?: number | string;\n  preserveAspectRatio?: string;\n  primitiveUnits?: number | string;\n  r?: number | string;\n  radius?: number | string;\n  refX?: number | string;\n  refY?: number | string;\n  renderingIntent?: number | string;\n  repeatCount?: number | string;\n  repeatDur?: number | string;\n  requiredExtensions?: number | string;\n  requiredFeatures?: number | string;\n  restart?: number | string;\n  result?: string;\n  rotate?: number | string;\n  rx?: number | string;\n  ry?: number | string;\n  scale?: number | string;\n  seed?: number | string;\n  shapeRendering?: number | string;\n  slope?: number | string;\n  spacing?: number | string;\n  specularConstant?: number | string;\n  specularExponent?: number | string;\n  speed?: number | string;\n  spreadMethod?: string;\n  startOffset?: number | string;\n  stdDeviation?: number | string;\n  stemh?: number | string;\n  stemv?: number | string;\n  stitchTiles?: number | string;\n  stopColor?: string;\n  stopOpacity?: number | string;\n  strikethroughPosition?: number | string;\n  strikethroughThickness?: number | string;\n  string?: number | string;\n  stroke?: string;\n  strokeDasharray?: string | number;\n  strokeDashoffset?: string | number;\n  strokeLinecap?: \"butt\" | \"round\" | \"square\" | \"inherit\";\n  strokeLinejoin?: \"miter\" | \"round\" | \"bevel\" | \"inherit\";\n  strokeMiterlimit?: string | number;\n  strokeOpacity?: number | string;\n  strokeWidth?: number | string;\n  surfaceScale?: number | string;\n  systemLanguage?: number | string;\n  tableValues?: number | string;\n  targetX?: number | string;\n  targetY?: number | string;\n  textAnchor?: string;\n  textDecoration?: number | string;\n  textLength?: number | string;\n  textRendering?: number | string;\n  to?: number | string;\n  transform?: string;\n  u1?: number | string;\n  u2?: number | string;\n  underlinePosition?: number | string;\n  underlineThickness?: number | string;\n  unicode?: number | string;\n  unicodeBidi?: number | string;\n  unicodeRange?: number | string;\n  unitsPerEm?: number | string;\n  vAlphabetic?: number | string;\n  values?: string;\n  vectorEffect?: number | string;\n  version?: string;\n  vertAdvY?: number | string;\n  vertOriginX?: number | string;\n  vertOriginY?: number | string;\n  vHanging?: number | string;\n  vIdeographic?: number | string;\n  viewBox?: string;\n  viewTarget?: number | string;\n  visibility?: number | string;\n  vMathematical?: number | string;\n  widths?: number | string;\n  wordSpacing?: number | string;\n  writingMode?: number | string;\n  x1?: number | string;\n  x2?: number | string;\n  x?: number | string;\n  xChannelSelector?: string;\n  xHeight?: number | string;\n  xlinkActuate?: string;\n  xlinkArcrole?: string;\n  xlinkHref?: string;\n  xlinkRole?: string;\n  xlinkShow?: string;\n  xlinkTitle?: string;\n  xlinkType?: string;\n  xmlBase?: string;\n  xmlLang?: string;\n  xmlns?: string;\n  xmlnsXlink?: string;\n  xmlSpace?: string;\n  y1?: number | string;\n  y2?: number | string;\n  y?: number | string;\n  yChannelSelector?: string;\n  z?: number | string;\n  zoomAndPan?: string;\n}\n\nexport interface PathAttributes {\n  d: string;\n}\n\nexport type TargetedEvent<\n  Target extends EventTarget = EventTarget,\n  TypedEvent extends Event = Event\n> = Omit<TypedEvent, \"currentTarget\"> & {\n  readonly currentTarget: Target;\n};\n\nexport type TargetedAnimationEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  AnimationEvent\n>;\nexport type TargetedClipboardEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  ClipboardEvent\n>;\nexport type TargetedCompositionEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  CompositionEvent\n>;\nexport type TargetedDragEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  DragEvent\n>;\nexport type TargetedFocusEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  FocusEvent\n>;\nexport type TargetedKeyboardEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  KeyboardEvent\n>;\nexport type TargetedMouseEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  MouseEvent\n>;\nexport type TargetedPointerEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  PointerEvent\n>;\nexport type TargetedTouchEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  TouchEvent\n>;\nexport type TargetedTransitionEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  TransitionEvent\n>;\nexport type TargetedUIEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  UIEvent\n>;\nexport type TargetedWheelEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  WheelEvent\n>;\nexport type TargetedInputEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  InputEvent\n>;\nexport type TargetedSecurityPolicyViolationEvent<Target extends EventTarget> = TargetedEvent<\n  Target,\n  SecurityPolicyViolationEvent\n>;\n\nexport interface EventHandler<E extends TargetedEvent> {\n  /**\n   * The `this` keyword always points to the DOM element the event handler\n   * was invoked on. See: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Event_handlers#Event_handlers_parameters_this_binding_and_the_return_value\n   */\n  (this: E[\"currentTarget\"], event: E): void;\n}\n\nexport type AnimationEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedAnimationEvent<Target>\n>;\nexport type ClipboardEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedClipboardEvent<Target>\n>;\nexport type CompositionEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedCompositionEvent<Target>\n>;\nexport type DragEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedDragEvent<Target>\n>;\nexport type FocusEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedFocusEvent<Target>\n>;\nexport type GenericEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedEvent<Target>\n>;\nexport type KeyboardEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedKeyboardEvent<Target>\n>;\nexport type MouseEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedMouseEvent<Target>\n>;\nexport type PointerEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedPointerEvent<Target>\n>;\nexport type TouchEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedTouchEvent<Target>\n>;\nexport type TransitionEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedTransitionEvent<Target>\n>;\nexport type UIEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedUIEvent<Target>\n>;\nexport type WheelEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedWheelEvent<Target>\n>;\nexport type InputEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedInputEvent<Target>\n>;\nexport type SecurityPolicyViolationEventHandler<Target extends EventTarget> = EventHandler<\n  TargetedSecurityPolicyViolationEvent<Target>\n>;\n\n/*\n  The function used to convert\n  a.split(\"\\n\")\n    .map(x => x.trim())\n    .map(x => \n      !x.includes(\":\") \n        ? x \n        : x.split(\":\")[0].toLowerCase() + \":\" + x.split(\":\")[1]\n    )\n    .filter(x => !x.includes(\"capture?:\"))\n    .join(\"\\n\")\n*/\n\nexport interface DOMAttributes<Target extends EventTarget>\n  extends ForgoDOMElementProps {\n  // Image Events\n  onload?: GenericEventHandler<Target>;\n  onerror?: GenericEventHandler<Target>;\n\n  // Clipboard Events\n  oncopy?: ClipboardEventHandler<Target>;\n  oncut?: ClipboardEventHandler<Target>;\n  onpaste?: ClipboardEventHandler<Target>;\n\n  // Composition Events\n  oncompositionend?: CompositionEventHandler<Target>;\n  oncompositionstart?: CompositionEventHandler<Target>;\n  oncompositionupdate?: CompositionEventHandler<Target>;\n\n  // Toggle Events\n  onbeforetoggle?: GenericEventHandler<Target>;\n  ontoggle?: GenericEventHandler<Target>;\n\n  // Focus Events\n  onfocus?: FocusEventHandler<Target>;\n  onfocusin?: FocusEventHandler<Target>;\n  onfocusout?: FocusEventHandler<Target>;\n  onblur?: FocusEventHandler<Target>;\n\n  // Input Events\n  onbeforeinput?: InputEventHandler<Target>;\n  oninput?: InputEventHandler<Target>;\n\n  // Form Events\n  oncancel?: GenericEventHandler<Target>;\n  onchange?: GenericEventHandler<Target>;\n  onsearch?: GenericEventHandler<Target>;\n  onsubmit?: GenericEventHandler<Target>;\n  oninvalid?: GenericEventHandler<Target>;\n  onreset?: GenericEventHandler<Target>;\n  onformdata?: GenericEventHandler<Target>;\n\n  // Keyboard Events\n  onkeydown?: KeyboardEventHandler<Target>;\n  /**\n   * @deprecated: This event is deprecated, use `onbeforeinput` or `onkeydown` instead.\n   * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/keypress_event\n   * */\n  onkeypress?: KeyboardEventHandler<Target>;\n  onkeyup?: KeyboardEventHandler<Target>;\n\n  // Media Events\n  onabort?: GenericEventHandler<Target>;\n  oncanplay?: GenericEventHandler<Target>;\n  oncanplaythrough?: GenericEventHandler<Target>;\n  ondurationchange?: GenericEventHandler<Target>;\n  onemptied?: GenericEventHandler<Target>;\n  onencrypted?: GenericEventHandler<Target>;\n  onended?: GenericEventHandler<Target>;\n  onloadeddata?: GenericEventHandler<Target>;\n  onloadedmetadata?: GenericEventHandler<Target>;\n  onloadstart?: GenericEventHandler<Target>;\n  onpause?: GenericEventHandler<Target>;\n  onplay?: GenericEventHandler<Target>;\n  onplaying?: GenericEventHandler<Target>;\n  onprogress?: GenericEventHandler<Target>;\n  onratechange?: GenericEventHandler<Target>;\n  onseeked?: GenericEventHandler<Target>;\n  onseeking?: GenericEventHandler<Target>;\n  onstalled?: GenericEventHandler<Target>;\n  onsuspend?: GenericEventHandler<Target>;\n  ontimeupdate?: GenericEventHandler<Target>;\n  onvolumechange?: GenericEventHandler<Target>;\n  onwaiting?: GenericEventHandler<Target>;\n\n  // Drag Events\n  ondrag?: DragEventHandler<Target>;\n  ondragend?: DragEventHandler<Target>;\n  ondragenter?: DragEventHandler<Target>;\n  ondragleave?: DragEventHandler<Target>;\n  ondragover?: DragEventHandler<Target>;\n  ondragstart?: DragEventHandler<Target>;\n  ondrop?: DragEventHandler<Target>;\n\n  // Mouse Events\n  ondblclick?: MouseEventHandler<Target>;\n  onmousedown?: MouseEventHandler<Target>;\n  onmouseenter?: MouseEventHandler<Target>;\n  onmouseleave?: MouseEventHandler<Target>;\n  onmousemove?: MouseEventHandler<Target>;\n  onmouseout?: MouseEventHandler<Target>;\n  onmouseover?: MouseEventHandler<Target>;\n  onmouseup?: MouseEventHandler<Target>;\n\n  // Selection Events\n  onselect?: GenericEventHandler<Target>;\n\n  // Touch Events\n  ontouchcancel?: TouchEventHandler<Target>;\n  ontouchend?: TouchEventHandler<Target>;\n  ontouchmove?: TouchEventHandler<Target>;\n  ontouchstart?: TouchEventHandler<Target>;\n\n  // Pointer Events\n  onauxclick?: PointerEventHandler<Target>;\n  onclick?: PointerEventHandler<Target>;\n  oncontextmenu?: PointerEventHandler<Target>;\n  ongotpointercapture?: PointerEventHandler<Target>;\n  onlostpointercapture?: PointerEventHandler<Target>;\n  onpointerover?: PointerEventHandler<Target>;\n  onpointerenter?: PointerEventHandler<Target>;\n  onpointerdown?: PointerEventHandler<Target>;\n  onpointermove?: PointerEventHandler<Target>;\n  onpointerup?: PointerEventHandler<Target>;\n  onpointercancel?: PointerEventHandler<Target>;\n  onpointerout?: PointerEventHandler<Target>;\n  onpointerleave?: PointerEventHandler<Target>;\n\n  // Wheel Events\n  onwheel?: WheelEventHandler<Target>;\n\n  // Scroll Events\n  onscroll?: GenericEventHandler<Target>;\n\n  // Security Policy Violation Events\n  onsecuritypolicyviolation?: SecurityPolicyViolationEventHandler<Target>;\n\n  // Animation Events\n  onanimationcancel?: AnimationEventHandler<Target>;\n  onanimationend?: AnimationEventHandler<Target>;\n  onanimationiteration?: AnimationEventHandler<Target>;\n  onanimationstart?: AnimationEventHandler<Target>;\n\n  // Transition Events\n  ontransitioncancel?: TransitionEventHandler<Target>;\n  ontransitionend?: TransitionEventHandler<Target>;\n  ontransitionrun?: TransitionEventHandler<Target>;\n  ontransitionstart?: TransitionEventHandler<Target>;\n}\n\nexport interface HTMLAttributes<RefType extends EventTarget = EventTarget>\n  // extends ForgoClassAttributes<RefType>,\n  extends DOMAttributes<RefType> {\n  [key: string]: any;\n  // Standard HTML Attributes\n  accept?: string;\n  acceptcharset?: string;\n  accesskey?: string;\n  action?: string;\n  allowfullscreen?: boolean;\n  allowtransparency?: boolean;\n  alt?: string;\n  as?: string;\n  async?: boolean;\n  autocomplete?: string;\n  autocorrect?: string;\n  autofocus?: boolean;\n  autoplay?: boolean;\n  cellpadding?: number | string;\n  cellspacing?: number | string;\n  charset?: string;\n  challenge?: string;\n  checked?: boolean;\n  class?: string;\n  classname?: string;\n  cols?: number;\n  colspan?: number;\n  content?: string;\n  contenteditable?: boolean;\n  contextmenu?: string;\n  controls?: boolean;\n  controlslist?: string;\n  coords?: string;\n  crossorigin?: string;\n  data?: string;\n  datetime?: string;\n  default?: boolean;\n  defer?: boolean;\n  dir?: \"auto\" | \"rtl\" | \"ltr\";\n  disabled?: boolean;\n  disableremoteplayback?: boolean;\n  download?: any;\n  draggable?: boolean;\n  enctype?: string;\n  form?: string;\n  formaction?: string;\n  formenctype?: string;\n  formmethod?: string;\n  formnovalidate?: boolean;\n  formtarget?: string;\n  frameborder?: number | string;\n  headers?: string;\n  height?: number | string;\n  hidden?: boolean;\n  high?: number;\n  href?: string;\n  hreflang?: string;\n  for?: string;\n  htmlfor?: string;\n  httpequiv?: string;\n  icon?: string;\n  id?: string;\n  inputmode?: string;\n  integrity?: string;\n  is?: string;\n  keyparams?: string;\n  keytype?: string;\n  kind?: string;\n  label?: string;\n  lang?: string;\n  list?: string;\n  loading?: \"eager\" | \"lazy\";\n  loop?: boolean;\n  low?: number;\n  manifest?: string;\n  marginheight?: number;\n  marginwidth?: number;\n  max?: number | string;\n  maxlength?: number;\n  media?: string;\n  mediagroup?: string;\n  method?: string;\n  min?: number | string;\n  minlength?: number;\n  multiple?: boolean;\n  muted?: boolean;\n  name?: string;\n  nonce?: string;\n  novalidate?: boolean;\n  open?: boolean;\n  optimum?: number;\n  pattern?: string;\n  placeholder?: string;\n  playsinline?: boolean;\n  poster?: string;\n  preload?: string;\n  radiogroup?: string;\n  readonly?: boolean;\n  rel?: string;\n  required?: boolean;\n  role?: string;\n  rows?: number;\n  rowspan?: number;\n  sandbox?: string;\n  scope?: string;\n  scoped?: boolean;\n  scrolling?: string;\n  seamless?: boolean;\n  selected?: boolean;\n  shape?: string;\n  size?: number;\n  sizes?: string;\n  slot?: string;\n  span?: number;\n  spellcheck?: boolean;\n  src?: string;\n  srcset?: string;\n  srcdoc?: string;\n  srclang?: string;\n  start?: number;\n  step?: number | string;\n  style?: string | CSSProperties;\n  summary?: string;\n  tabindex?: number;\n  target?: string;\n  title?: string;\n  type?: string;\n  usemap?: string;\n  value?: string | string[] | number;\n  volume?: string | number;\n  width?: number | string;\n  wmode?: string;\n  wrap?: string;\n\n  // RDFa Attributes\n  about?: string;\n  datatype?: string;\n  inlist?: any;\n  prefix?: string;\n  property?: string;\n  resource?: string;\n  typeof?: string;\n  vocab?: string;\n\n  // Microdata Attributes\n  itemprop?: string;\n  itemscope?: boolean;\n  itemtype?: string;\n  itemid?: string;\n  itemref?: string;\n}\n\nexport interface HTMLMarqueeElement extends HTMLElement {\n  behavior?: \"scroll\" | \"slide\" | \"alternate\";\n  bgcolor?: string;\n  direction?: \"left\" | \"right\" | \"up\" | \"down\";\n  height?: number | string;\n  hspace?: number | string;\n  loop?: number | string;\n  scrollamount?: number | string;\n  scrolldelay?: number | string;\n  truespeed?: boolean;\n  vspace?: number | string;\n  width?: number | string;\n}\n\nexport type IntrinsicElements = {\n  // We have to omit the SVG anchor element because both HTML and SVG have an\n  // 'a' tag, and TSX gets confused about which one you mean if you have onclick\n  // handlers. Sadly that means things get weird if you try to use the SVG\n  // anchor tag, but I don't know how to support both.\n  [el in keyof Omit<SVGElementTagNameMap, \"a\">]: HTMLAttributes<\n    SVGElementTagNameMap[el]\n  >;\n} & {\n  [el in keyof HTMLElementTagNameMap]: HTMLAttributes<\n    HTMLElementTagNameMap[el]\n  >;\n};\n"
  },
  {
    "path": "src/test/README.md",
    "content": "# Running Tests\n\nTo run tests, cd into this directory ('tests').\n\nAnd then:\n\n```sh\n./build.sh && npm test\n```\n"
  },
  {
    "path": "src/test/afterRender/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport {\n  counterX10,\n  currentNode,\n  previousNode,\n  renderAgain,\n  run,\n  runWithTextNode,\n  runWithRef,\n  runWithDangerouslySetInnerHtml,\n} from \"./script.js\";\nimport should from \"should\";\n\nexport default function () {\n  describe(\"runs afterRender()\", () => {\n    it(\"when mounted on a DOM element\", async () => {\n      const dom = new JSDOM(htmlFile(), {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      });\n      const window = dom.window;\n\n      run(dom);\n\n      await new Promise<void>((resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve();\n        });\n      });\n\n      should.equal(previousNode as Element, undefined);\n      should.equal(counterX10, 10);\n      should.equal((currentNode as Element).getAttribute(\"prop\"), \"hello\");\n      renderAgain();\n      should.equal((previousNode as Element).nodeType, 1);\n      should.equal(counterX10, 20);\n      should.equal((currentNode as Element).getAttribute(\"prop\"), \"world\");\n      should.equal((previousNode as Element).getAttribute(\"prop\"), \"hello\");\n      renderAgain();\n      should.equal((previousNode as Element).nodeType, 1);\n      should.equal(counterX10, 30);\n      should.equal((currentNode as Element).getAttribute(\"prop\"), \"world\");\n      should.equal((previousNode as Element).getAttribute(\"prop\"), \"world\");\n    });\n\n    it(\"when mounted on a text node\", async () => {\n      const dom = new JSDOM(htmlFile(), {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      });\n      const window = dom.window;\n\n      runWithTextNode(dom);\n\n      await new Promise<void>((resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve();\n        });\n      });\n\n      should.equal(previousNode as Element, undefined);\n      should.equal(counterX10, 10);\n      renderAgain();\n      should.equal((previousNode as Element).nodeType, 3);\n      should.equal(counterX10, 20);\n      renderAgain();\n      should.equal((previousNode as Element).nodeType, 3);\n      should.equal(counterX10, 30);\n    });\n  });\n\n  describe(\"setting an element's attributes\", () => {\n    it(\"skips the 'ref' attribute\", async () => {\n      const dom = new JSDOM(htmlFile(), {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      });\n      const window = dom.window;\n\n      runWithRef(dom);\n\n      await new Promise<void>((resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve();\n        });\n      });\n\n      should.equal((currentNode as Element).getAttribute(\"ref\"), undefined);\n    });\n\n    it(\"skips the 'dangerouslySetInnerHtml' attribute\", async () => {\n      const dom = new JSDOM(htmlFile(), {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      });\n      const window = dom.window;\n\n      runWithDangerouslySetInnerHtml(dom);\n\n      await new Promise<void>((resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve();\n        });\n      });\n\n      should.equal(\n        (currentNode as Element).getAttribute(\"dangerouslySetInnerHTML\"),\n        undefined\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/afterRender/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv, Component } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nlet component: Component<{}>;\n\nexport function renderAgain() {\n  component.update();\n}\n\nexport let currentNode: ChildNode | undefined;\nexport let previousNode: ChildNode | undefined;\nexport let counterX10: number;\n\nconst TestComponent = () => {\n  let counter: number = 0;\n  component = new Component({\n    render() {\n      counter++;\n      return counter === 1 ? (\n        <div id=\"hello\" prop=\"hello\">\n          Hello world\n        </div>\n      ) : (\n        <p id=\"hello\" prop=\"world\">\n          Hello world\n        </p>\n      );\n    },\n  });\n  component.afterRender((_props, previousNode_, component) => {\n    currentNode = component.__internal.element.node;\n    previousNode = previousNode_;\n    counterX10 = counter * 10;\n  });\n  return component;\n};\n\nfunction ComponentOnTextNode() {\n  let counter: number = 0;\n  component = new Component({\n    render() {\n      counter++;\n      return \"Hello world\";\n    },\n  });\n  component.afterRender((_props, previousNode_, component) => {\n    currentNode = component.__internal.element.node;\n    previousNode = previousNode_;\n    counterX10 = counter * 10;\n  });\n  return component;\n}\n\nconst ComponentWithRef = () => {\n  const ref: forgo.ForgoRef<HTMLDivElement> = {};\n  const component = new forgo.Component({\n    render() {\n      return <div ref={ref} />;\n    },\n  });\n  component.afterRender((_props, _previousNode, component) => {\n    currentNode = component.__internal.element.node;\n  });\n  return component;\n};\n\nconst ComponentWithDangerouslySetInnerHTML = () => {\n  const component = new forgo.Component({\n    render() {\n      return <div dangerouslySetInnerHTML={{ __html: \"<div></div>\" }} />;\n    },\n  });\n  component.afterRender((_props, _previousNode, component) => {\n    currentNode = component.__internal.element.node;\n  });\n  return component;\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<TestComponent />, window.document.getElementById(\"root\"));\n  });\n}\n\nexport function runWithTextNode(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<ComponentOnTextNode />, window.document.getElementById(\"root\"));\n  });\n}\n\nexport function runWithRef(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<ComponentWithRef />, window.document.getElementById(\"root\"));\n  });\n}\nexport function runWithDangerouslySetInnerHtml(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(\n      <ComponentWithDangerouslySetInnerHTML />,\n      window.document.getElementById(\"root\")\n    );\n  });\n}\n"
  },
  {
    "path": "src/test/assertIsComponent/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport should from \"should\";\nimport { componentError, run } from \"./script.js\";\n\nexport default function () {\n  it(\"asserts if ctor returns a component\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<void>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve();\n      });\n    });\n\n    (componentError as Error).message.should.equal(\n      \"BasicComponent component constructor must return an instance of the Component class\"\n    );\n  });\n}\n"
  },
  {
    "path": "src/test/assertIsComponent/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nfunction BasicComponent() {\n  return <div>Hello world</div>;\n}\n\nexport let componentError: any = undefined;\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    try {\n      mount(<BasicComponent />, document.getElementById(\"root\"));\n    } catch (ex) {\n      componentError = ex;\n    }\n  });\n}\n"
  },
  {
    "path": "src/test/boundary/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { run } from \"./script.js\";\n\nexport default function () {\n  it(\"honors error boundary\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    const innerHtml = await new Promise<string>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve(window.document.body.innerHTML);\n      });\n    });\n\n    innerHtml.should.containEql(\n      \"Error in ErrorComponent: Some error occurred :(\"\n    );\n  });\n}\n"
  },
  {
    "path": "src/test/boundary/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { Component, mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nconst ErrorComponent = () => {\n  return new forgo.Component({\n    render() {\n      throw new Error(\"Some error occurred :(\");\n    },\n  });\n};\n\ninterface ErrorBoundaryComponentProps extends forgo.ForgoElementBaseProps {\n  name: string;\n}\n\nconst ErrorBoundary = (props: ErrorBoundaryComponentProps) => {\n  return new Component<ErrorBoundaryComponentProps>({\n    render({ children }) {\n      return <div>{children}</div>;\n    },\n    error(props, error) {\n      return (\n        <p>\n          Error in {props.name}: {(error as Error).message}\n        </p>\n      );\n    },\n  });\n};\n\nconst App = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <div>\n          <ErrorBoundary name=\"ErrorComponent\">\n            <ErrorComponent />\n          </ErrorBoundary>\n        </div>\n      );\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<App />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/childWithFragmentUnmounts/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { numUnmounts, renderAgain, run } from \"./script.js\";\nimport should from \"should\";\n\nexport default function () {\n  it(\"runs unmount on child returning a fragment\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<void>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve();\n      });\n    });\n\n    renderAgain();\n\n    should.equal(numUnmounts, 1);\n  });\n}\n"
  },
  {
    "path": "src/test/childWithFragmentUnmounts/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\nlet counter = 0;\n\nexport let numUnmounts = 0;\n\nlet component: forgo.Component<{}>;\n\nexport function renderAgain() {\n  component.update();\n}\n\nconst TestComponent = () => {\n  component = new forgo.Component({\n    render() {\n      counter++;\n      return counter === 1 ? <Child /> : <p>1</p>;\n    },\n  });\n  return component;\n};\n\nconst Child = () => {\n  const component = new forgo.Component({\n    render() {\n      return (\n        <>\n          <div>1</div>\n          <div>2</div>\n          <div>3</div>\n        </>\n      );\n    },\n  });\n  component.unmount(() => {\n    numUnmounts++;\n  });\n  return component;\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<TestComponent />, window.document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/clearsOldProps/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport should from \"should\";\nimport { renderAgain, run } from \"./script.js\";\n\nexport default function () {\n  it(\"clears old props\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<string>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        renderAgain();\n        resolve(window.document.body.innerHTML);\n      });\n    });\n\n    const elem = window.document.getElementById(\"mydiv\");\n    should.not.exist((elem as Element).getAttribute(\"prop1\"));\n    should.equal((elem as Element).getAttribute(\"prop2\"), \"world\");\n  });\n}\n"
  },
  {
    "path": "src/test/clearsOldProps/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nlet component: forgo.Component<{}>;\n\nexport function renderAgain() {\n  component.update();\n}\n\nconst BasicComponent = () => {\n  let firstRender = true;\n  component = new forgo.Component({\n    render() {\n      if (firstRender) {\n        firstRender = false;\n        return (\n          <div id=\"mydiv\" prop1=\"hello\">\n            Hello world\n          </div>\n        );\n      } else {\n        return (\n          <div id=\"mydiv\" prop2=\"world\">\n            Hello world\n          </div>\n        );\n      }\n    },\n  });\n  return component;\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<BasicComponent />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/componentApi.tsx",
    "content": "import * as assert from \"assert\";\nimport should from \"should\";\n\nimport * as forgo from \"../index.js\";\nimport { run } from \"./componentRunner.js\";\n\nfunction componentFactory() {\n  const state: {\n    component: forgo.Component<TestComponentProps> | null;\n    wrapper: forgo.Component<WrapperProps> | null;\n\n    render: {\n      key: string;\n      args: [TestComponentProps, forgo.Component<TestComponentProps>];\n    }[];\n    mount: {\n      key: string;\n      args: [TestComponentProps, forgo.Component<TestComponentProps>];\n    }[];\n    unmount: {\n      key: string;\n      args: [TestComponentProps, forgo.Component<TestComponentProps>];\n    }[];\n    afterRender: {\n      key: string;\n      args: [\n        TestComponentProps,\n        ChildNode | undefined,\n        forgo.Component<TestComponentProps>\n      ];\n    }[];\n    shouldUpdate: {\n      key: string;\n      args: [\n        TestComponentProps,\n        TestComponentProps,\n        forgo.Component<TestComponentProps>\n      ];\n    }[];\n  } = {\n    component: null,\n    wrapper: null,\n\n    render: [],\n    mount: [],\n    unmount: [],\n    afterRender: [],\n    shouldUpdate: [],\n  };\n\n  interface TestComponentProps {\n    foo: string;\n    forceUpdate?: boolean;\n  }\n  const TestComponent = (_props: TestComponentProps) => {\n    const component = new forgo.Component<TestComponentProps>({\n      render(...args) {\n        state.render.push({ key: \"render\", args });\n        return <p>Hello, world!</p>;\n      },\n    });\n    component.mount((...args) => state.mount.push({ key: \"mount1\", args }));\n    component.mount((...args) => {\n      state.mount.push({ key: \"mount2\", args });\n\n      component.unmount((...args) =>\n        state.unmount.push({ key: \"unmount3\", args })\n      );\n    });\n\n    component.unmount((...args) =>\n      state.unmount.push({ key: \"unmount1\", args })\n    );\n    component.unmount((...args) =>\n      state.unmount.push({ key: \"unmount2\", args })\n    );\n\n    component.afterRender((...args) =>\n      state.afterRender.push({ key: \"afterRender1\", args })\n    );\n    component.afterRender((...args) =>\n      state.afterRender.push({ key: \"afterRender2\", args })\n    );\n\n    component.shouldUpdate((...args) => {\n      state.shouldUpdate.push({ key: \"shouldUpdate1\", args });\n      return false;\n    });\n    component.shouldUpdate((...args) => {\n      state.shouldUpdate.push({ key: \"shouldUpdate2\", args });\n      return args[0].forceUpdate ?? false;\n    });\n\n    state.component = component;\n\n    return component;\n  };\n\n  interface WrapperProps extends TestComponentProps {\n    unmount?: boolean;\n  }\n  /**\n   * We use the wrapper to have a way for tests to unmount the actual component\n   * we're testing\n   */\n  const Wrapper = (props: WrapperProps) => {\n    const wrapper = new forgo.Component<WrapperProps>({\n      render(props) {\n        if (props.unmount) return <p>Unmounted!</p>;\n        return <TestComponent {...props} />;\n      },\n    });\n    state.wrapper = wrapper;\n    return wrapper;\n  };\n\n  return {\n    Component: Wrapper,\n    state: state,\n  };\n}\n\nexport default function () {\n  describe(\"The component API\", () => {\n    it(\"errors out if the ctor doesn't return a Component instance\", async () => {\n      const Ctor = () => ({});\n\n      await assert.rejects(run(() => <Ctor />));\n    });\n\n    it(\"passes the right arguments to render()\", async () => {\n      const { Component, state } = componentFactory();\n      await run((env) => <Component {...env} foo=\"foo\" />);\n\n      should.equal(state.render.length, 1);\n      should.equal(state.render[0].args[0].foo, \"foo\");\n      should.equal(state.render[0].args[1], state.component);\n    });\n\n    it(\"passes the right arguments to mount event listeners\", async () => {\n      const { Component, state } = componentFactory();\n      await run((env) => <Component {...env} foo=\"foo\" />);\n\n      should.equal(state.mount.length, 2);\n      should.equal(state.mount[0].args[0].foo, \"foo\");\n      should.equal(state.mount[0].args[1], state.component);\n      should.deepEqual(\n        state.mount.map(({ key }) => key),\n        [\"mount1\", \"mount2\"]\n      );\n    });\n\n    it(\"passes the right arguments to afterRender event listeners\", async () => {\n      const { Component, state } = componentFactory();\n      await run((env) => <Component {...env} foo=\"foo\" />);\n\n      should.equal(state.afterRender.length, 2);\n      should.equal(state.afterRender[0].args[0].foo, \"foo\");\n      should.equal(state.afterRender[0].args[1], undefined);\n      should.equal(state.afterRender[0].args[2], state.component);\n      should.deepEqual(\n        state.afterRender.map(({ key }) => key),\n        [\"afterRender1\", \"afterRender2\"]\n      );\n    });\n\n    it(\"passes the right arguments to shouldUpdate event listeners\", async () => {\n      const { Component, state } = componentFactory();\n      await run((env) => <Component {...env} foo=\"foo\" />);\n\n      state.component!.update();\n\n      should.equal(state.shouldUpdate.length, 2);\n      should.equal(state.shouldUpdate[0].args[0].foo, \"foo\");\n      should.equal(state.shouldUpdate[0].args[1].foo, \"foo\");\n      should.equal(state.shouldUpdate[0].args[2], state.component);\n      should.deepEqual(\n        state.shouldUpdate.map(({ key }) => key),\n        [\"shouldUpdate1\", \"shouldUpdate2\"]\n      );\n    });\n\n    it(\"only rerenders the component if at least one shouldUpdate listeners return true\", async () => {\n      const { Component, state } = componentFactory();\n      await run((env) => <Component {...env} foo=\"foo\" />);\n\n      state.component!.update();\n      should.equal(state.render.length, 1);\n\n      state.component!.update({ foo: \"foo\", forceUpdate: true });\n      should.equal(state.render.length, 2);\n    });\n\n    it(\"passes the right arguments to unmount event listeners\", async () => {\n      const { Component, state } = componentFactory();\n      await run((env) => <Component {...env} foo=\"foo\" />);\n\n      state.wrapper!.update({ foo: \"foo\", unmount: true });\n\n      // 3, sir! Because we want to catch not only the two top-level listeners,\n      // but also the listener added by the mount event listener.\n      should.equal(state.unmount.length, 3);\n      should.equal(state.unmount[0].args[0].foo, \"foo\");\n      should.equal(state.unmount[0].args[1], state.component);\n      should.deepEqual(\n        state.unmount.map(({ key }) => key),\n        [\"unmount1\", \"unmount2\", \"unmount3\"]\n      );\n    });\n  });\n\n  describe(\"The legacy component API\", () => {\n    it(\"still works\", async () => {\n      interface Props {\n        foo: number;\n      }\n      let mounted: Props | null = null;\n      let unmounted: Props | null = null;\n      let rendered: Props | null = null;\n      let afterRender: Props | null = null;\n      let shouldUpdate: Props | null = null;\n\n      const LegacyComponent: forgo.ForgoSimpleComponentCtor<Props> = () => {\n        return {\n          mount(props) {\n            mounted = props;\n          },\n          unmount(props) {\n            unmounted = props;\n          },\n          render(props) {\n            rendered = props;\n            return <p>Hello, world!</p>;\n          },\n          afterRender(props) {\n            afterRender = props;\n          },\n          shouldUpdate(props) {\n            shouldUpdate = props;\n            return true;\n          },\n        };\n      };\n\n      interface ParentProps {\n        renderChild: boolean;\n      }\n      let component: forgo.Component<ParentProps>;\n\n      const ParentComponent = (initialProps: ParentProps) => {\n        component = new forgo.Component<ParentProps>({\n          render({ renderChild }) {\n            if (renderChild) return <LegacyComponent foo={1} />;\n            return null;\n          },\n        });\n        return component;\n      };\n\n      await run(() => <ParentComponent renderChild={true} />);\n      // We have to render once just to render, then render again to kick\n      // shouldUpdate, then render again to kick unmount\n      component!.update();\n      component!.update({ renderChild: false });\n\n      should.deepEqual(mounted!.foo, 1);\n      should.deepEqual(unmounted!.foo, 1);\n      should.deepEqual(rendered!.foo, 1);\n      should.deepEqual(afterRender!.foo, 1);\n      should.deepEqual(shouldUpdate!.foo, 1);\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/componentFragment/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { run, runNested } from \"./script.js\";\nimport should from \"should\";\n\nexport default function () {\n  describe(\"renders component returning fragments\", () => {\n    it(\"top level fragment\", async () => {\n      const dom = new JSDOM(htmlFile(), {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      });\n      const window = dom.window;\n\n      run(dom);\n\n      await new Promise<void>((resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve();\n        });\n      });\n\n      const rootElem = window.document.getElementById(\"root\") as HTMLElement;\n      rootElem.innerHTML.should.containEql(\n        \"<div>1</div><div>2</div><div>3</div>\"\n      );\n    });\n\n    it(\"nested fragment\", async () => {\n      const dom = new JSDOM(htmlFile(), {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      });\n      const window = dom.window;\n\n      runNested(dom);\n\n      await new Promise<void>((resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve();\n        });\n      });\n\n      const rootElem = window.document.getElementById(\"root\") as HTMLElement;\n      rootElem.innerHTML.should.containEql(\n        \"<div>1</div><div>2</div><div>3</div><div>4</div>\"\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/componentFragment/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nconst TestComponent = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <>\n          <div>1</div>\n          <div>2</div>\n          <div>3</div>\n        </>\n      );\n    },\n  });\n};\n\nconst NestedFragmentComponent = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <>\n          <>\n            <div>1</div>\n            <div>2</div>\n          </>\n          <>\n            <div>3</div>\n          </>\n          <div>4</div>\n        </>\n      );\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<TestComponent />, window.document.getElementById(\"root\"));\n  });\n}\n\nexport function runNested(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<NestedFragmentComponent />, window.document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/componentKeepsStateWhenReordered/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { run } from \"./script.js\";\nimport { componentStates, reorderComponents } from \"./script.js\";\n\nexport default function () {\n  it(\"components maintain state when reordered\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n\n    const window = dom.window;\n\n    run(dom);\n\n    const componentStatesFirstRender = await new Promise<Map<unknown, string>>(\n      (resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve(new Map(Array.from(componentStates)));\n        });\n      }\n    );\n\n    reorderComponents();\n\n    // We explicitly test with a falsey value (zero) to catch if we use the\n    // shorthand `if (key)` rather than the required `if (key !== undefined)`\n    [0, \"1\", \"2\", \"3\", \"4\", \"5\"].forEach((key) => {\n      componentStates\n        .get(key)!\n        .should.equal(\n          componentStatesFirstRender.get(key),\n          `component with key=${key} state is mismatched`\n        );\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/componentKeepsStateWhenReordered/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nfunction getRandomString() {\n  return (\n    Math.random().toString(36).substring(2, 10) +\n    Math.random().toString(36).substring(2, 10)\n  );\n}\n\nexport const componentStates = new Map<unknown, string>();\n\ninterface StatefulComponentProps {\n  key: unknown;\n}\nconst StatefulComponent = () => {\n  let state = getRandomString();\n  const component = new forgo.Component<StatefulComponentProps>({\n    render({ key }) {\n      componentStates.set(key, state);\n      return (\n        <p state={state} key={key}>\n          Component #{key}\n        </p>\n      );\n    },\n  });\n  component.unmount(({ key }) => {\n    componentStates.delete(key);\n  });\n  return component;\n};\n\nlet sortOrder = 1;\nlet containerComponent: forgo.Component<StatefulComponentProps>;\n\nexport function reorderComponents() {\n  sortOrder = 2;\n  containerComponent.update({ key: undefined });\n}\n\nconst ContainerComponent = () => {\n  containerComponent = new forgo.Component({\n    render() {\n      componentStates.clear();\n      return (\n        <div>\n          {sortOrder === 1 ? (\n            <>\n              <StatefulComponent key={0} />\n              <StatefulComponent key=\"1\" />\n              <StatefulComponent key=\"2\" />\n              <StatefulComponent key=\"3\" />\n              <StatefulComponent key=\"4\" />\n              <StatefulComponent key=\"5\" />\n            </>\n          ) : (\n            <>\n              <StatefulComponent key=\"1\" />\n              <StatefulComponent key=\"4\" />\n              <StatefulComponent key=\"3\" />\n              <StatefulComponent key={0} />\n              <StatefulComponent key=\"2\" />\n              <StatefulComponent key=\"5\" />\n            </>\n          )}\n        </div>\n      );\n    },\n  });\n  return containerComponent;\n};\n\nexport function run(dom: JSDOM) {\n  const window = dom.window;\n  const document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<ContainerComponent />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/componentMount.tsx",
    "content": "import should from \"should\";\n\nimport * as forgo from \"../index.js\";\nimport { run } from \"./componentRunner.js\";\n\nconst componentFactory = () => {\n  const state: {\n    parentEl: forgo.ForgoRef<HTMLDivElement>;\n    idAttr: string | null;\n    parentChildrenCount: number;\n  } = { parentEl: {}, parentChildrenCount: 0, idAttr: null };\n  const TestComponent = () => {\n    const Child = () => {\n      const component = new forgo.Component({\n        render() {\n          return <div>Hello world</div>;\n        },\n      });\n      component.mount(() => {\n        state.idAttr = state.parentEl.value!.getAttribute(\"id\");\n      });\n      return component;\n    };\n\n    const component = new forgo.Component({\n      render() {\n        return (\n          <div ref={state.parentEl} id=\"hello\">\n            <Child />\n          </div>\n        );\n      },\n    });\n    component.mount(() => {\n      state.parentChildrenCount = state.parentEl.value!.childNodes.length;\n    });\n\n    return component;\n  };\n\n  return { state, TestComponent };\n};\n\nconst recursiveComponentFactory = () => {\n  const state = {\n    mountCount: 0,\n    renderCount: 0,\n  };\n\n  const TestComponent = () => {\n    const component = new forgo.Component({\n      render() {\n        state.renderCount += 1;\n        return <div id=\"hello\"></div>;\n      },\n    });\n    component.mount(() => {\n      state.mountCount += 1;\n      component.update();\n    });\n\n    return component;\n  };\n\n  return { state, TestComponent };\n};\n\nexport default function () {\n  describe(\"Component mount event\", async () => {\n    it(\"runs mount() when a component is attached to node\", async () => {\n      const { state, TestComponent } = componentFactory();\n      await run(() => <TestComponent />);\n\n      should.equal(state.parentEl.value!.id, \"hello\");\n    });\n\n    it(\"renders the parent's attributes before calling the child's mount()\", async () => {\n      const { state, TestComponent } = componentFactory();\n      await run(() => <TestComponent />);\n\n      should.equal(state.idAttr, \"hello\");\n    });\n\n    it(\"renders all descendants before calling the parent's mount()\", async () => {\n      const { state, TestComponent } = componentFactory();\n      await run(() => <TestComponent />);\n\n      should.equal(state.parentChildrenCount, 1);\n    });\n\n    it(\"doesn't fire twice if the component updates during mount\", async () => {\n      const { state, TestComponent } = recursiveComponentFactory();\n      await run(() => <TestComponent />);\n\n      should.equal(state.renderCount, 2);\n      should.equal(state.mountCount, 1);\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/componentRunner.tsx",
    "content": "import * as forgo from \"../index.js\";\nimport htmlFile from \"./htmlFile.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\n\nexport interface ComponentEnvironment {\n  window: DOMWindow;\n  document: Document;\n}\n\nfunction defaultDom() {\n  return new JSDOM(htmlFile(), {\n    runScripts: \"outside-only\",\n    resources: \"usable\",\n  });\n}\n\n/**\n * Receives a forgo component and renders it.\n * @param componentFactory Accepts some props to attach to the component and\n * returns something that will be passed directly to mount()\n * @param dom Defaults to creating a new JSDOM instance, but maybe for some\n * reason a test needs to create its own DOM?\n * @returns The constructed JSDOM globals, plus an object holding values the\n * component under test has exposed to the test\n *\n * We use a component factory instead of directly receiving the component\n * because we want tests to be able to set their own per-test props on a\n * component, which only works if the test declares the props as JSX\n */\nexport async function run(\n  componentBuilder: (env: ComponentEnvironment) => {\n    node: forgo.ForgoNode;\n  },\n  dom: JSDOM = defaultDom()\n): Promise<{\n  dom: JSDOM;\n  document: Document;\n  window: DOMWindow;\n}> {\n  const window = dom.window;\n  const document = window.document;\n  forgo.setCustomEnv({ window, document });\n\n  const node = componentBuilder({ window, document });\n\n  // Wait for the component to actually render\n  await new Promise<void>((resolve, reject) => {\n    window.addEventListener(\"load\", () => {\n      try {\n        forgo.mount(node, document.getElementById(\"root\"));\n        resolve();\n      } catch (ex) {\n        reject(ex);\n      }\n    });\n  });\n\n  return { dom, document, window };\n}\n"
  },
  {
    "path": "src/test/componentUnmount.tsx",
    "content": "import should from \"should\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\n\nimport * as forgo from \"../index.js\";\nimport { run } from \"./componentRunner.js\";\n\nimport htmlFile from \"./htmlFile.js\";\n\nconst componentFactory = () => {\n  const state = {\n    parentUnmounts: 0,\n    childUnmounts: 0,\n    component: null as forgo.Component<{}> | null,\n  };\n\n  const Parent = () => {\n    let firstRender = true;\n\n    state.component = new forgo.Component({\n      render() {\n        if (firstRender) {\n          firstRender = false;\n          return <Child />;\n        } else {\n          return <div>The child should have unmounted.</div>;\n        }\n      },\n    });\n\n    state.component.unmount(() => {\n      state.parentUnmounts += 1;\n    });\n    return state.component;\n  };\n\n  const Child = () => {\n    const component = new forgo.Component({\n      render() {\n        return <div>This is the child component</div>;\n      },\n    });\n    component.unmount(() => {\n      state.childUnmounts += 1;\n    });\n    return component;\n  };\n\n  return { state, TestComponent: Parent };\n};\n\nexport default function () {\n  it(\"runs unmount() when a child component goes away\", async () => {\n    const { state, TestComponent } = componentFactory();\n    await run(() => <TestComponent />);\n\n    state.component!.update();\n\n    should.equal(state.childUnmounts, 1);\n  });\n\n  it(\"unmounts the component tree when forgo.unmount() is called\", async () => {\n    const { state, TestComponent } = componentFactory();\n    // Use a fragment to be sure we handle unmounting more than one root component\n    const { document } = await run(() => (\n      <>\n        <TestComponent />\n        <TestComponent />\n      </>\n    ));\n\n    forgo.unmount(document.getElementById(\"root\")!);\n    should.equal(state.parentUnmounts, 2);\n    should.equal(state.childUnmounts, 2);\n    should.equal(document.getElementById(\"root\")!.childNodes.length, 0);\n  });\n}\n"
  },
  {
    "path": "src/test/css/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { run } from \"./script.js\";\n\nexport default function () {\n  it(\"applies css styles\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    const innerHtml = await new Promise<string>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve(window.document.body.innerHTML);\n      });\n    });\n\n    innerHtml.should.containEql(\n      `<ul><li style=\"background-color: green; padding: 10px;\">One</li><li style=\"background-color: green; padding: 10px;\">Two</li></ul>`\n    );\n  });\n}\n"
  },
  {
    "path": "src/test/css/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nconst TestComponent = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <div>\n          <ul>\n            <li style={{ backgroundColor: \"green\", padding: \"10px\" }}>One</li>\n            <li style={{ backgroundColor: \"green\", padding: \"10px\" }}>Two</li>\n          </ul>\n        </div>\n      );\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<TestComponent />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/dangerouslySetInnerHTML/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { run } from \"./script.js\";\n\nexport default function () {\n  it(\"sets innerHTML if dangerouslySetInnerHTML is defined\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    const innerHtml = await new Promise<string>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve(window.document.body.innerHTML);\n      });\n    });\n\n    innerHtml.should.containEql(\"<p>Hello world</p>\");\n  });\n}\n"
  },
  {
    "path": "src/test/dangerouslySetInnerHTML/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nconst BasicComponent = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <div dangerouslySetInnerHTML={{ __html: `<p>Hello world</p>` }}></div>\n      );\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<BasicComponent />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/elementKeepsStateWhenReordered/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport {\n  inputRef1,\n  inputRef2,\n  inputRef3,\n  inputRef4,\n  inputRef5,\n  run,\n} from \"./script.js\";\nimport { reorderElements } from \"./script.js\";\n\nexport default function () {\n  it(\"element maintains state with reordered\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<void>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve();\n      });\n    });\n\n    reorderElements();\n\n    (inputRef1.value as HTMLInputElement).id.should.equal(\"inputnew1\");\n    (inputRef2.value as HTMLInputElement).id.should.equal(\"inputnew2\");\n    (inputRef3.value as HTMLInputElement).id.should.equal(\"inputnew3\");\n    (inputRef4.value as HTMLInputElement).id.should.equal(\"inputnew4\");\n    (inputRef5.value as HTMLInputElement).id.should.equal(\"inputnew5\");\n  });\n}\n"
  },
  {
    "path": "src/test/elementKeepsStateWhenReordered/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet sortOrder = 1;\nlet component: forgo.Component<{}>;\n\nexport function reorderElements() {\n  sortOrder = 2;\n  component.update({});\n}\n\nexport let inputRef1: forgo.ForgoRef<HTMLInputElement> = {};\nexport let inputRef2: forgo.ForgoRef<HTMLInputElement> = {};\nexport let inputRef3: forgo.ForgoRef<HTMLInputElement> = {};\nexport let inputRef4: forgo.ForgoRef<HTMLInputElement> = {};\nexport let inputRef5: forgo.ForgoRef<HTMLInputElement> = {};\n\nconst ContainerComponent = () => {\n  component = new forgo.Component({\n    render() {\n      return (\n        <div>\n          {sortOrder === 1 ? (\n            <>\n              <input type=\"text\" key=\"1\" id=\"inputold1\" ref={inputRef1} />\n              <input type=\"text\" key=\"2\" id=\"inputold2\" ref={inputRef2} />\n              <input type=\"text\" key=\"3\" id=\"inputold3\" ref={inputRef3} />\n              <input type=\"text\" key=\"4\" id=\"inputold4\" ref={inputRef4} />\n              <input type=\"text\" key=\"5\" id=\"inputold5\" ref={inputRef5} />\n            </>\n          ) : (\n            <>\n              <input type=\"text\" id=\"inputnew1\" key=\"1\" />\n              <input type=\"text\" id=\"inputnew4\" key=\"4\" />\n              <input type=\"text\" id=\"inputnew3\" key=\"3\" />\n              <input type=\"text\" id=\"inputnew2\" key=\"2\" />\n              <input type=\"text\" id=\"inputnew5\" key=\"5\" />\n            </>\n          )}\n        </div>\n      );\n    },\n  });\n  return component;\n};\n\nexport function run(dom: JSDOM) {\n  const window = dom.window;\n  const document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<ContainerComponent />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/elementRef/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport should from \"should\";\nimport { inputRef, run } from \"./script.js\";\n\nexport default function () {\n  it(\"attaches element refs\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<void>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve();\n      });\n    });\n\n    should.equal((inputRef.value as HTMLInputElement).tagName, \"INPUT\");\n  });\n}\n"
  },
  {
    "path": "src/test/elementRef/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv, ForgoRef } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nexport let inputRef: ForgoRef<HTMLInputElement> = {};\n\nconst Parent = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <div>\n          <input type=\"text\" ref={inputRef} />\n        </div>\n      );\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<Parent />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/fragmentMountEvent.tsx",
    "content": "import should from \"should\";\n\nimport * as forgo from \"../index.js\";\nimport { run } from \"./componentRunner.js\";\nimport type { ForgoRef } from \"../index.js\";\n\n// We should only call those pendingMounts after a component renders, not after\n// elements render. I guess? Or maybe, only after component || array renders?\n\nfunction componentFactory() {\n  const state: {\n    elementBoundAtMountTime: boolean | null;\n  } = {\n    elementBoundAtMountTime: null,\n  };\n\n  const TestComponent = () => {\n    const el: ForgoRef<HTMLDivElement> = {};\n\n    const component = new forgo.Component({\n      render(_props) {\n        return (\n          <>\n            <p>Ignore Me</p>\n            <p ref={el}>Mount shouldn't fire until I'm created</p>\n          </>\n        );\n      },\n    });\n    component.mount(() => {\n      state.elementBoundAtMountTime = Boolean(el.value);\n    });\n    return component;\n  };\n\n  return {\n    TestComponent,\n    state,\n  };\n}\n\nexport default function () {\n  describe(\"Fragment mount event\", () => {\n    it(\"doesn't fire until *all* of the fragment's children have been created\", async () => {\n      const { TestComponent, state } = componentFactory();\n      await run(() => <TestComponent />);\n\n      should.equal(state.elementBoundAtMountTime, true);\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/fragmentOverwriteDoesNotUnmount/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { renderAgain, run, unmountCounter } from \"./script.js\";\nimport should from \"should\";\n\nexport default function () {\n  it(\"does not unmount when fragment is overwritten\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<void>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve();\n      });\n    });\n\n    renderAgain();\n    renderAgain();\n\n    should.equal(unmountCounter, 0);\n    window.document.body.innerHTML.should.containEql(\n      \"<p>5</p><p>6</p><p>7</p>\"\n    );\n  });\n}\n"
  },
  {
    "path": "src/test/fragmentOverwriteDoesNotUnmount/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\nlet counter = 0;\n\nlet component: forgo.Component<{}>;\nexport let unmountCounter: number = 0;\n\nexport function renderAgain() {\n  component.update();\n}\n\nconst TestComponent = () => {\n  component = new forgo.Component({\n    render() {\n      counter++;\n      return counter === 1 ? (\n        <>\n          <div>1</div>\n          <div>2</div>\n          <div>3</div>\n        </>\n      ) : counter === 2 ? (\n        <p>4</p>\n      ) : (\n        <>\n          <p>5</p>\n          <p>6</p>\n          <p>7</p>\n        </>\n      );\n    },\n  });\n  component.unmount(() => {\n    unmountCounter++;\n  });\n  return component;\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<TestComponent />, window.document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/fragmentUnmountRunsOnce/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { renderAgain, run, unmountCounter } from \"./script.js\";\nimport should from \"should\";\n\nexport default function () {\n  it(\"runs fragment unmount only once\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<void>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve();\n      });\n    });\n\n    renderAgain();\n\n    should.equal(unmountCounter, 0);\n  });\n}\n"
  },
  {
    "path": "src/test/fragmentUnmountRunsOnce/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\nlet counter = 0;\n\nlet component: forgo.Component<{}>;\nexport let unmountCounter: number = 0;\n\nexport function renderAgain() {\n  component.update();\n}\n\nconst TestComponent = () => {\n  component = new forgo.Component({\n    render() {\n      counter++;\n      return counter === 1 ? (\n        <>\n          <div>1</div>\n          <div>2</div>\n          <div>3</div>\n        </>\n      ) : (\n        <>\n          <p>1</p>\n          <p>2</p>\n          <p>3</p>\n        </>\n      );\n    },\n  });\n  component.unmount(() => {\n    unmountCounter++;\n  });\n  return component;\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<TestComponent />, window.document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/htmlFile.ts",
    "content": "export default function html(prerendered?: string) {\n  return `\n  <!DOCTYPE HTML>\n  <html lang=\"en\">\n    <head>\n      <title>Zerok Test</title>\n    </head>\n    <body>\n      <div id=\"root\">${prerendered || \"\"}</div>\n    </body>\n  </html>\n  `;\n}\n"
  },
  {
    "path": "src/test/hydrate/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { buttonRef, run } from \"./script.js\";\n\nexport default function () {\n  it(\"hydrates\", async () => {\n    const dom = new JSDOM(\n      htmlFile(`\n      <div>\n        <button>\n          Click me!\n        </button>\n        <p>Clicked 0 times</p>\n      </div>\n    `),\n      {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      }\n    );\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<void>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve();\n      });\n    });\n\n    (buttonRef as any).value.click();\n    (buttonRef as any).value.click();\n    (buttonRef as any).value.click();\n\n    window.document.body.innerHTML.should.containEql(\"Clicked 3 times\");\n  });\n}\n"
  },
  {
    "path": "src/test/hydrate/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { render, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nexport let buttonRef: any = {};\n\nconst TestComponent = () => {\n  let counter = 0;\n\n  return new forgo.Component({\n    render(_props: any, component) {\n      function updateCounter() {\n        counter++;\n        component.update();\n      }\n\n      return (\n        <div>\n          <button onclick={updateCounter} ref={buttonRef}>\n            Click me!\n          </button>\n          <p>Clicked {counter} times</p>\n        </div>\n      );\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  const { node } = render(<TestComponent />);\n  window.addEventListener(\"load\", () => {\n    document.getElementById(\"root\")!.firstElementChild!.replaceWith(node);\n  });\n}\n"
  },
  {
    "path": "src/test/inheritedCustomElement/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { run } from \"./script.js\";\nexport default function () {\n  it(\"works with inherited custom elements\", async () => {\n    let calledConnectedCallback = false;\n\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    class WordCount extends window.HTMLParagraphElement {\n      connectedCallback() {\n        calledConnectedCallback = true;\n        // count words in element's parent element\n        let wcParent = this.parentNode;\n\n        function countWords(node: any) {\n          let text = node.innerText || node.textContent;\n          return text.split(/\\s+/g).length;\n        }\n\n        let count = \"Words: \" + countWords(wcParent);\n\n        // Create a shadow root\n        let shadow = this.attachShadow({ mode: \"open\" });\n\n        // Create text node and add word count to it\n        let text = window.document.createElement(\"span\");\n        text.textContent = count;\n\n        // Append it to the shadow root\n        shadow.appendChild(text);\n      }\n    }\n\n    window.customElements.define(\"word-count\", WordCount, { extends: \"p\" });\n\n    run(dom);\n\n    const innerHtml = await new Promise<string>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve(window.document.body.innerHTML);\n      });\n    });\n\n    calledConnectedCallback.should.be.true();\n  });\n}\n"
  },
  {
    "path": "src/test/inheritedCustomElement/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nconst TestComponent = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <div>\n          <article contenteditable={true}>\n            <h2>Sample heading</h2>\n\n            <p>\n              Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc\n              pulvinar sed justo sed viverra. Aliquam ac scelerisque tellus.\n              Vivamus porttitor nunc vel nibh rutrum hendrerit. Donec viverra\n              vestibulum pretium. Mauris at eros vitae ante pellentesque\n              bibendum. Etiam et blandit purus, nec aliquam libero. Etiam leo\n              felis, pulvinar et diam id, sagittis pulvinar diam. Nunc\n              pellentesque rutrum sapien, sed faucibus urna sodales in. Sed\n              tortor nisl, egestas nec egestas luctus, faucibus vitae purus. Ut\n              elit nunc, pretium eget fermentum id, accumsan et velit. Sed\n              mattis velit diam, a elementum nunc facilisis sit amet.\n            </p>\n\n            <p is=\"word-count\"></p>\n          </article>\n        </div>\n      );\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<TestComponent />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/keyedFragmentsPreserveChildStates/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { renderAgain, run } from \"./script.js\";\nimport should from \"should\";\n\nexport default function () {\n  /**\n   * If a keyed component reterns a Fragment, the states of all children of the\n   * fragment should be preserved upon rerender.\n   */\n  it(\"keyed Fragments preserve all children\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<void>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve();\n      });\n    });\n\n    const getStates = (targets: \"swappable\" | \"fixed-position\") => {\n      const els = Array.from(\n        window.document.getElementsByClassName(\"stateful-grandchild\")\n      );\n\n      const targetEls =\n        targets === \"swappable\"\n          ? els.filter((el) => {\n              const attr = el.getAttribute(\"data-key\");\n              return attr === \"first-child\" || attr === \"second-child\";\n            })\n          : [els[els.length - 1]];\n\n      const found = targetEls.map((el) => el.getAttribute(\"data-state\"));\n      // Sanity check, because the tests pass against an empty array\n      // TODO: search by key, not class\n      if (found.length === 0) {\n        throw new Error(\"Should have found elements\");\n      }\n      return found;\n    };\n\n    // Capture the original states\n    const grandchildrenStatePass1 = getStates(\"swappable\");\n\n    renderAgain();\n\n    const grandchildrenStatePass2 = getStates(\"swappable\");\n\n    should.deepEqual(\n      grandchildrenStatePass2.reverse(),\n      grandchildrenStatePass1,\n      \"Grandchildren states should be exactly reversed from the first render\"\n    );\n\n    renderAgain();\n\n    const grandchildrenStatePass3 = getStates(\"swappable\");\n\n    should.deepEqual(\n      grandchildrenStatePass3,\n      grandchildrenStatePass1,\n      \"Grandchildren states should be identical to the first render\"\n    );\n  });\n}\n"
  },
  {
    "path": "src/test/keyedFragmentsPreserveChildStates/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nexport function renderAgain() {\n  elementOrder = !elementOrder;\n  component.update();\n}\n\nlet component: forgo.Component<{}>;\nlet elementOrder = true;\nconst Parent = () => {\n  component = new forgo.Component({\n    render() {\n      const keys = elementOrder\n        ? [\"first-child\", \"second-child\"]\n        : [\"second-child\", \"first-child\"];\n\n      return (\n        <>\n          <Child key={keys[0]} />\n          <Child key={keys[1]} />\n          <Child key=\"last-child\" />\n        </>\n      );\n    },\n  });\n  return component;\n};\n\ninterface ChildProps {\n  key?: unknown;\n}\nconst Child = () => {\n  const state = Math.random().toString();\n\n  return new forgo.Component<ChildProps>({\n    render(props) {\n      return (\n        <>\n          <p\n            class=\"stateful-grandchild\"\n            data-state={state}\n            data-key={props.key}\n          >\n            Hello, world!\n          </p>\n          {props.key ? <Child /> : null}\n        </>\n      );\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<Parent />, window.document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/mount/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport should from \"should\";\nimport { run, runQuerySelector } from \"./script.js\";\n\nexport default function () {\n  describe(\"mounts a component\", () => {\n    it(\"mounts on an DOM element\", async () => {\n      const dom = new JSDOM(htmlFile(), {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      });\n      const window = dom.window;\n\n      run(dom);\n\n      const innerHtml = await new Promise<string>((resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve(window.document.body.innerHTML);\n        });\n      });\n\n      innerHtml.should.containEql(\"Hello world\");\n    });\n\n    it(\"mounts using query selector\", async () => {\n      const dom = new JSDOM(htmlFile(), {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      });\n      const window = dom.window;\n\n      runQuerySelector(dom);\n\n      const innerHtml = await new Promise<string>((resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve(window.document.body.innerHTML);\n        });\n      });\n\n      innerHtml.should.containEql(\"Hello world\");\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/mount/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nconst BasicComponent = () => {\n  return new forgo.Component({\n    render() {\n      return <div>Hello world</div>;\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<BasicComponent />, document.getElementById(\"root\"));\n  });\n}\n\nexport function runQuerySelector(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<BasicComponent />, \"#root\");\n  });\n}\n"
  },
  {
    "path": "src/test/mountRunsOnceWhenChildRendersFragment/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { mountCounter, renderAgain, run } from \"./script.js\";\nimport should from \"should\";\n\nexport default function () {\n  it(\"runs mount only once when child renders fragment\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<void>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve();\n      });\n    });\n\n    renderAgain();\n\n    should.equal(mountCounter, 1);\n  });\n}\n"
  },
  {
    "path": "src/test/mountRunsOnceWhenChildRendersFragment/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\nlet counter = 0;\n\nlet component: forgo.Component<{}>;\nexport let mountCounter: number = 0;\n\nexport function renderAgain() {\n  component.update();\n}\n\nconst TestComponent = () => {\n  component = new forgo.Component({\n    render() {\n      counter++;\n      return <SuperCompo />;\n    },\n  });\n  component.mount(() => {\n    mountCounter++;\n  });\n  return component;\n};\n\nconst SuperCompo = () => {\n  return new forgo.Component({\n    render() {\n      return counter === 1 ? (\n        <>\n          <div>1</div>\n          <div>2</div>\n          <div>3</div>\n        </>\n      ) : (\n        <>\n          <p>1</p>\n          <p>2</p>\n          <p>3</p>\n        </>\n      );\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<TestComponent />, window.document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/mountRunsOnceWhenRenderingFragment/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { mountCounter, renderAgain, run } from \"./script.js\";\nimport should from \"should\";\n\nexport default function () {\n  it(\"runs mount only once when rendering fragment\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<void>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve();\n      });\n    });\n\n    renderAgain();\n\n    should.equal(mountCounter, 1);\n  });\n}\n"
  },
  {
    "path": "src/test/mountRunsOnceWhenRenderingFragment/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\nlet counter = 0;\n\nlet component: forgo.Component<{}>;\n\nexport function renderAgain() {\n  component.update();\n}\n\nexport let mountCounter = 0;\n\nconst TestComponent = () => {\n  component = new forgo.Component({\n    render() {\n      counter++;\n      return counter === 1 ? (\n        <>\n          <div>1</div>\n          <div>2</div>\n          <div>3</div>\n        </>\n      ) : (\n        <>\n          <p>1</p>\n          <p>2</p>\n          <p>3</p>\n        </>\n      );\n    },\n  });\n  component.mount(() => {\n    mountCounter++;\n  });\n  return component;\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<TestComponent />, window.document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/nodeState/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport should from \"should\";\nimport { ForgoRef } from \"../../index.js\";\nimport { run } from \"./script.js\";\n\nexport default function () {\n  it(\"attaches state correctly\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<ForgoRef<HTMLElement>>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve(window.myInput);\n      });\n    });\n\n    const greetingDiv = window.greetingDiv.value;\n    should.exist(greetingDiv.__forgo);\n    should.equal(greetingDiv.__forgo.components.length, 2);\n    should.equal(greetingDiv.__forgo.key, \"mydiv\");\n    should.equal(\n      greetingDiv.__forgo.components[0].component.__internal.element\n        .componentIndex,\n      0\n    );\n    should.equal(\n      greetingDiv.__forgo.components[1].component.__internal.element\n        .componentIndex,\n      1\n    );\n  });\n}\n"
  },
  {
    "path": "src/test/nodeState/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nconst Parent = () => {\n  return new forgo.Component({\n    render() {\n      return <Greet name=\"kai\" />;\n    },\n  });\n};\n\ninterface GreetProps {\n  name: string;\n}\nconst Greet = (props: GreetProps) => {\n  window.greetingDiv = {};\n\n  return new forgo.Component<GreetProps>({\n    render(props) {\n      return (\n        <div key=\"mydiv\" ref={window.greetingDiv}>\n          Hello {props.name}\n        </div>\n      );\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<Parent />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/package1.json",
    "content": "{\n  \"name\": \"forgo-test-suite\",\n  \"version\": \"1.4.9\",\n  \"private\": \"true\"\n}\n"
  },
  {
    "path": "src/test/passProps/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport should from \"should\";\nimport { run } from \"./script.js\";\n\nexport default function () {\n  it(\"passes props to child\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    const innerHtml = await new Promise<string>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve(window.document.body.innerHTML);\n      });\n    });\n\n    innerHtml.should.containEql(\"Hello\");\n  });\n}\n"
  },
  {
    "path": "src/test/passProps/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nconst Parent = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <div>\n          <Greet text=\"Hello\" />\n        </div>\n      );\n    },\n  });\n};\n\ninterface GreetProps {\n  text: string;\n}\nconst Greet = (_initialProps: GreetProps) => {\n  return new forgo.Component<GreetProps>({\n    render(props: { text: string }) {\n      return <div>{props.text}</div>;\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<Parent />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/propsChanges/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { mutatedProps, renderAgain, run } from \"./script.js\";\nimport should from \"should\";\n\nexport default function () {\n  describe(\"props changes\", () => {\n    it(\"doesn't set props if they haven't changed\", async () => {\n      const dom = new JSDOM(htmlFile(), {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      });\n      const window = dom.window;\n\n      run(dom);\n\n      await new Promise<void>((resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve();\n        });\n      });\n\n      should.equal(mutatedProps[\"id\"], undefined);\n      should.equal(mutatedProps[\"x-id\"], undefined);\n      should.equal(mutatedProps[\"prop\"], undefined);\n      renderAgain();\n      // We have to give the event loop a chance to run, because our test will\n      // run synchronously before the MutationObserver fires and records any\n      // changes\n      await new Promise((resolve) => queueMicrotask(() => resolve(null)));\n      should.equal(\n        mutatedProps[\"id\"],\n        undefined,\n        \"id prop should not have been mutated\"\n      );\n      should.equal(\n        mutatedProps[\"x-id\"],\n        undefined,\n        \"x-id attribute should not have been mutated\"\n      );\n      // This is a canary. If this fails, it means our MutationObserver wasn't\n      // set up correctly, and we can't trust the prior assertions.\n      should.equal(\n        mutatedProps[\"prop\"],\n        true,\n        \"MutationObserver was not set up correctly\"\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/propsChanges/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nlet component: forgo.Component<{}>;\n\nexport function renderAgain() {\n  component.update();\n}\n\nexport let currentNode: Element | undefined;\nexport let previousNode: Element | undefined;\nexport let counterX10 = 0;\nexport let mutatedProps: {\n  [key: string]: boolean;\n} = {};\n\nconst TestComponent = () => {\n  let counter: number = 0;\n\n  const el: forgo.ForgoRef<HTMLDivElement> = {};\n  mutatedProps = {};\n\n  component = new forgo.Component({\n    render() {\n      counter++;\n      return (\n        <div id=\"hello\" prop={counter === 1 ? \"hello\" : \"world\"} ref={el}>\n          Hello world\n        </div>\n      );\n    },\n  });\n  component.mount(() => {\n    // Detect each time attributes are changed (after the first render)\n    const observer = new window.MutationObserver((mutations) => {\n      const elMutation = mutations.find(\n        (mutation) => mutation.target === el.value\n      );\n      if (elMutation?.attributeName) {\n        mutatedProps[elMutation.attributeName] = true;\n      }\n    });\n    observer.observe(el.value!, { attributes: true });\n  });\n  component.afterRender((_props: any, previousNode, component) => {\n    currentNode = component.__internal.element.node as Element;\n    previousNode = previousNode as Element;\n    counterX10 = counter * 10;\n  });\n  return component;\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<TestComponent />, window.document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/renderPrimitives/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport should from \"should\";\nimport {\n  Wrapping,\n  runWithBooleanProps,\n  runWithNullProps,\n  runWithNumericProps,\n  runWithStringProps,\n  runWithUndefinedProps,\n} from \"./script.js\";\n\nconst TEXT_NODE_TYPE = 3;\n\nexport default function () {\n  describe(\"renders primitives\", () => {\n    const wrapping: Wrapping[] = [\"DIV\", \"FRAGMENT\", \"NONE\"];\n    wrapping.forEach((wrapping) => {\n      const wrappedText =\n        wrapping === \"DIV\"\n          ? \" wrapped in DIV\"\n          : wrapping === \"FRAGMENT\"\n          ? \" wrapped in FRAGMENT\"\n          : \" without wrapping\";\n\n      it(\"renders undefined\" + wrappedText, async () => {\n        const dom = new JSDOM(htmlFile(), {\n          runScripts: \"outside-only\",\n          resources: \"usable\",\n        });\n        const window = dom.window;\n\n        runWithUndefinedProps(dom, wrapping);\n\n        await new Promise<void>((resolve) => {\n          window.addEventListener(\"load\", () => {\n            resolve();\n          });\n        });\n\n        if (wrapping === \"DIV\") {\n          should.equal(\n            window.document.getElementById(\"mydiv\")?.childNodes.length,\n            0\n          );\n        } else {\n          should.equal(\n            window.document.getElementById(\"root\")?.childNodes.length,\n            1\n          );\n          should.equal(\n            window.document.getElementById(\"root\")?.childNodes[0].nodeType,\n            8\n          );\n        }\n      });\n\n      it(\"renders null\" + wrappedText, async () => {\n        const dom = new JSDOM(htmlFile(), {\n          runScripts: \"outside-only\",\n          resources: \"usable\",\n        });\n        const window = dom.window;\n\n        runWithNullProps(dom, wrapping);\n\n        await new Promise<void>((resolve) => {\n          window.addEventListener(\"load\", () => {\n            resolve();\n          });\n        });\n\n        if (wrapping === \"DIV\") {\n          should.equal(\n            window.document.getElementById(\"mydiv\")?.childNodes.length,\n            0\n          );\n        } else {\n          should.equal(\n            window.document.getElementById(\"root\")?.childNodes.length,\n            1\n          );\n          should.equal(\n            window.document.getElementById(\"root\")?.childNodes[0].nodeType,\n            8\n          );\n        }\n      });\n\n      it(\"renders string\" + wrappedText, async () => {\n        const dom = new JSDOM(htmlFile(), {\n          runScripts: \"outside-only\",\n          resources: \"usable\",\n        });\n        const window = dom.window;\n\n        runWithStringProps(dom, wrapping);\n\n        await new Promise<void>((resolve) => {\n          window.addEventListener(\"load\", () => {\n            resolve();\n          });\n        });\n\n        window.document\n          .getElementById(\"mydiv\")\n          ?.innerHTML.should.equal(\"hello\");\n      });\n\n      it(\"renders boolean\" + wrappedText, async () => {\n        const dom = new JSDOM(htmlFile(), {\n          runScripts: \"outside-only\",\n          resources: \"usable\",\n        });\n        const window = dom.window;\n\n        runWithBooleanProps(dom, wrapping);\n\n        await new Promise<void>((resolve) => {\n          window.addEventListener(\"load\", () => {\n            resolve();\n          });\n        });\n\n        window.document.getElementById(\"mydiv\")?.innerHTML.should.equal(\"true\");\n      });\n\n      it(\"renders number\" + wrappedText, async () => {\n        const dom = new JSDOM(htmlFile(), {\n          runScripts: \"outside-only\",\n          resources: \"usable\",\n        });\n        const window = dom.window;\n\n        runWithNumericProps(dom, wrapping);\n\n        await new Promise<void>((resolve) => {\n          window.addEventListener(\"load\", () => {\n            resolve();\n          });\n        });\n\n        window.document.getElementById(\"mydiv\")?.innerHTML.should.equal(\"100\");\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/renderPrimitives/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\ntype ComponentProps = {\n  value: string | number | boolean | object | null | BigInt | undefined;\n};\n\nexport type Wrapping = \"DIV\" | \"NONE\" | \"FRAGMENT\";\n\nconst ComponentReturningWrappedPrimitive = (_props: ComponentProps) => {\n  return new forgo.Component<ComponentProps>({\n    render(props: ComponentProps) {\n      return <div id=\"mydiv\">{props.value}</div>;\n    },\n  });\n};\n\nconst ComponentReturningPrimitive = (_props: ComponentProps) => {\n  return new forgo.Component<ComponentProps>({\n    render(props: ComponentProps) {\n      return props.value;\n    },\n  });\n};\n\nconst ComponentReturningPrimitiveInFragment = (_props: ComponentProps) => {\n  return new forgo.Component<ComponentProps>({\n    render(props: ComponentProps) {\n      return <>{props.value}</>;\n    },\n  });\n};\n\nexport function runWithUndefinedProps(dom: JSDOM, wrapping: Wrapping) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(\n      wrapping === \"DIV\" ? (\n        <ComponentReturningWrappedPrimitive value={undefined} />\n      ) : wrapping === \"FRAGMENT\" ? (\n        <ComponentReturningPrimitiveInFragment value={undefined} />\n      ) : (\n        <ComponentReturningPrimitive value={undefined} />\n      ),\n      document.getElementById(\"root\")\n    );\n  });\n}\n\nexport function runWithNullProps(dom: JSDOM, wrapping: Wrapping) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(\n      wrapping === \"DIV\" ? (\n        <ComponentReturningWrappedPrimitive value={null} />\n      ) : wrapping === \"FRAGMENT\" ? (\n        <ComponentReturningPrimitiveInFragment value={null} />\n      ) : (\n        <ComponentReturningPrimitive value={null} />\n      ),\n      \"#root\"\n    );\n  });\n}\n\nexport function runWithStringProps(dom: JSDOM, wrapping: Wrapping) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(\n      wrapping === \"DIV\" ? (\n        <ComponentReturningWrappedPrimitive value={\"hello\"} />\n      ) : wrapping === \"FRAGMENT\" ? (\n        <ComponentReturningPrimitiveInFragment value={\"hello\"} />\n      ) : (\n        <ComponentReturningPrimitive value={\"hello\"} />\n      ),\n      \"#root\"\n    );\n  });\n}\n\nexport function runWithBooleanProps(dom: JSDOM, wrapping: Wrapping) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(\n      wrapping === \"DIV\" ? (\n        <ComponentReturningWrappedPrimitive value={true} />\n      ) : wrapping === \"FRAGMENT\" ? (\n        <ComponentReturningPrimitiveInFragment value={true} />\n      ) : (\n        <ComponentReturningPrimitive value={true} />\n      ),\n      \"#root\"\n    );\n  });\n}\n\nexport function runWithNumericProps(dom: JSDOM, wrapping: Wrapping) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(\n      wrapping === \"DIV\" ? (\n        <ComponentReturningWrappedPrimitive value={100} />\n      ) : wrapping === \"FRAGMENT\" ? (\n        <ComponentReturningPrimitiveInFragment value={100} />\n      ) : (\n        <ComponentReturningPrimitive value={100} />\n      ),\n      \"#root\"\n    );\n  });\n}\n"
  },
  {
    "path": "src/test/rendersArraysInChildren/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { runArrays, runNestedArrays } from \"./script.js\";\n\nexport default function () {\n  describe(\"renders arrays as DOM node children\", () => {\n    const tests: [string, (dom: JSDOM) => void][] = [\n      [\"array\", runArrays],\n      [\"nested array\", runNestedArrays],\n    ];\n\n    tests.forEach(([testName, run]) => {\n      it(testName, async () => {\n        const dom = new JSDOM(htmlFile(), {\n          runScripts: \"outside-only\",\n          resources: \"usable\",\n        });\n        const window = dom.window;\n\n        run(dom);\n\n        const innerHtml = await new Promise<string>((resolve) => {\n          window.addEventListener(\"load\", () => {\n            resolve(window.document.body.innerHTML);\n          });\n        });\n\n        innerHtml.should.containEql(\n          \"<div>Hello world<p>1</p><p>2</p><p>3</p><p>4</p></div>\"\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/rendersArraysInChildren/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nconst someIntegers = [1, 2, 3, 4];\n\nconst BasicComponent = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <div>\n          Hello world\n          {someIntegers.map((i) => (\n            <p>{i}</p>\n          ))}\n        </div>\n      );\n    },\n  });\n};\n\nexport function runArrays(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<BasicComponent />, document.getElementById(\"root\"));\n  });\n}\n\nconst BasicComponentNested = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <div>\n          Hello world\n          {someIntegers.map((i) => [[[<p>{i}</p>]]])}\n        </div>\n      );\n    },\n  });\n};\n\nexport function runNestedArrays(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<BasicComponentNested />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/replaceByKey/index.ts",
    "content": "import { DOMWindow, JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport should from \"should\";\nimport {\n  renderAgain,\n  runObjectKey,\n  runStringKey,\n  unmountedElements,\n} from \"./script.js\";\n\nexport default function () {\n  describe(\"replacement by key\", () => {\n    const tests: [string, (dom: JSDOM) => void][] = [\n      [\"string key\", runStringKey],\n      [\"object key\", runObjectKey],\n    ];\n    tests.forEach(([testName, run]) => {\n      it(`replaces a child by ${testName}`, async () => {\n        const dom = new JSDOM(htmlFile(), {\n          runScripts: \"outside-only\",\n          resources: \"usable\",\n        });\n        const window = dom.window;\n\n        run(dom);\n\n        await new Promise<void>((resolve) => {\n          window.addEventListener(\"load\", () => {\n            resolve();\n          });\n        });\n        \n        renderAgain();\n\n        window.document.body.innerHTML.should.containEql(\"Hello 2X\");\n        should.deepEqual(unmountedElements, [\"1\", \"3\"]);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/replaceByKey/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\ntype ParentProps = {\n  keys: {\n    key: any;\n    id: string;\n  }[];\n};\n\nexport let unmountedElements: string[] = [];\n\nlet component: forgo.Component<ParentProps>;\n\nexport function renderAgain() {\n  component.update();\n}\n\nconst Parent = (props: ParentProps) => {\n  unmountedElements = [];\n  let firstRender = true;\n  component = new forgo.Component<ParentProps>({\n    render(props) {\n      if (firstRender) {\n        firstRender = false;\n        return (\n          <div>\n            {props.keys.map((k) => (\n              <Child key={k.key} id={k.id} />\n            ))}\n          </div>\n        );\n      } else {\n        return (\n          <div>\n            <Child key={props.keys[1].key} id={props.keys[1].id + \"X\"} />\n          </div>\n        );\n      }\n    },\n  });\n  return component;\n};\n\ninterface ChildProps {\n  key: any;\n  id: string;\n}\nconst Child = (props: ChildProps) => {\n  let myId = \"NA\";\n\n  const component = new forgo.Component<ChildProps>({\n    render(props) {\n      myId = props.id;\n      return <div>Hello {props.id}</div>;\n    },\n  });\n  component.unmount(() => {\n    unmountedElements.push(myId);\n  });\n  return component;\n};\n\nexport function runStringKey(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(\n      <Parent\n        keys={[\n          {\n            key: \"1\",\n            id: \"1\",\n          },\n          {\n            key: \"2\",\n            id: \"2\",\n          },\n          {\n            key: \"3\",\n            id: \"3\",\n          },\n        ]}\n      />,\n      document.getElementById(\"root\")\n    );\n  });\n}\n\nexport function runObjectKey(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  const keyOne = { x: 1 };\n  const keyTwo = { x: 2 };\n  const keyThree = { x: 3 };\n\n  window.addEventListener(\"load\", () => {\n    mount(\n      <Parent\n        keys={[\n          {\n            key: keyOne,\n            id: \"1\",\n          },\n          {\n            key: keyTwo,\n            id: \"2\",\n          },\n          {\n            key: keyThree,\n            id: \"3\",\n          },\n        ]}\n      />,\n      document.getElementById(\"root\")\n    );\n  });\n}\n"
  },
  {
    "path": "src/test/replacingFragmentWithNodeWontUnmount/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { run, unmountCounter, renderAgain } from \"./script.js\";\nimport should from \"should\";\n\nexport default function () {\n  it(\"runs unmount when fragment is replaced with a node\", async () => {\n    const dom = new JSDOM(htmlFile(), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n    const window = dom.window;\n\n    run(dom);\n\n    await new Promise<void>((resolve) => {\n      window.addEventListener(\"load\", () => {\n        resolve();\n      });\n    });\n\n    renderAgain();\n\n    should.equal(unmountCounter, 0);\n  });\n}\n"
  },
  {
    "path": "src/test/replacingFragmentWithNodeWontUnmount/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\nlet counter = 0;\n\nlet component: forgo.Component<{}>;\nexport function renderAgain() {\n  component.update();\n}\n\nexport let unmountCounter = 0;\n\nconst TestComponent = () => {\n  component = new forgo.Component({\n    render() {\n      counter++;\n      return counter === 1 ? (\n        <>\n          <div>1</div>\n          <div>2</div>\n          <div>3</div>\n        </>\n      ) : (\n        <p>1</p>\n      );\n    },\n  });\n  component.unmount(() => {\n    unmountCounter++;\n  });\n  return component;\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<TestComponent />, window.document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/rerender.tsx",
    "content": "import should from \"should\";\n\nimport * as forgo from \"../index.js\";\nimport { run } from \"./componentRunner.js\";\nimport type { ForgoRef } from \"../index.js\";\nimport { DOMWindow } from \"jsdom\";\n\nconst unmanagedNodeTagName = \"article\";\n\nfunction componentFactory() {\n  const state: {\n    buttonElement: ForgoRef<HTMLButtonElement>;\n    rootElement: ForgoRef<HTMLDivElement>;\n    subrootElement: ForgoRef<HTMLDivElement>;\n    pElement: ForgoRef<HTMLParagraphElement>;\n    remove: () => void;\n  } = {\n    buttonElement: {},\n    rootElement: {},\n    subrootElement: {},\n    pElement: {},\n    remove: () => {},\n  };\n\n  interface TestComponentProps {\n    insertionPosition?: \"first\" | \"last\" | number;\n    window: DOMWindow;\n    document: Document;\n  }\n  const TestComponent = (props: TestComponentProps) => {\n    let counter = 0;\n    let insertAfterRender = true;\n    const { insertionPosition, document } = props;\n\n    const addChild = (el: HTMLElement, tag: string) => {\n      if (!insertionPosition) return;\n\n      // We create an <article> so that it's very obvious if a bug causes forgo to\n      // treat the node as a managed dom node and transform its props/children\n      const newElement = document.createElement(unmanagedNodeTagName);\n      newElement.setAttribute(\"data-forgo\", tag);\n      switch (insertionPosition) {\n        case \"first\":\n          el.prepend(newElement);\n          break;\n        case \"last\":\n          el.append(newElement);\n          break;\n        default:\n          el.insertBefore(\n            newElement,\n            Array.from(el.childNodes)[insertionPosition]\n          );\n      }\n    };\n\n    const component = new forgo.Component<TestComponentProps>({\n      render(_props, component) {\n        function updateCounter() {\n          counter++;\n          component.update();\n        }\n        state.remove = () => {\n          state.rootElement\n            .value!.querySelectorAll(\"[data-forgo]\")\n            .forEach((el) => el.remove());\n          // The test that uses this asserts that all these nodes are gone, so\n          // don't add one after we call update(). We have to call update() to\n          // know that Forgo doesn't misbehave when nodes it saw before disappear.\n          insertAfterRender = false;\n          component.update();\n        };\n\n        return (\n          <div ref={state.rootElement} id=\"root\">\n            <button\n              onclick={updateCounter}\n              ref={state.buttonElement}\n              id=\"button\"\n            >\n              Click me!\n            </button>\n            <p id=\"p\" ref={state.pElement}>\n              Clicked {counter} times\n            </p>\n            <div ref={state.subrootElement} id=\"subRoot\"></div>\n          </div>\n        );\n      },\n    });\n    component.afterRender(() => {\n      if (!insertAfterRender) return;\n      addChild(state.rootElement.value!, \"child-of-root\");\n      if (insertionPosition === \"first\") {\n        addChild(state.subrootElement.value!, \"child-of-subroot\");\n      }\n    });\n    return component;\n  };\n\n  return {\n    Component: TestComponent,\n    state,\n  };\n}\n\nexport default function () {\n  describe(\"rerendering\", () => {\n    it(\"rerenders\", async () => {\n      const {\n        Component,\n        state: { buttonElement },\n      } = componentFactory();\n      const { window } = await run((env) => <Component {...env} />);\n\n      buttonElement.value!.click();\n      buttonElement.value!.click();\n      buttonElement.value!.click();\n\n      window.document.body.innerHTML.should.containEql(\"Clicked 3 times\");\n    });\n\n    /**\n     * When a consuming application uses the DOM APIs to add children to an\n     * element, those children should be left in place after rendering if they're\n     * not specified as part of the JSX.\n     */\n    it(\"preserves children inserted with DOM APIs that are prepended to the front of the children list\", async () => {\n      const { Component, state } = componentFactory();\n\n      await run((env) => (\n        <Component insertionPosition={\"first\" as const} {...env} />\n      ));\n\n      should.equal(state.subrootElement.value!.children.length, 1);\n      should.equal(\n        state.rootElement.value!.querySelectorAll(\n          '[data-forgo=\"child-of-root\"]'\n        ).length,\n        1,\n        \"Root element should have the appended element\"\n      );\n\n      state.buttonElement.value!.click();\n\n      should.equal(state.subrootElement.value!.children.length, 2);\n      should.equal(\n        state.rootElement.value!.querySelectorAll(\n          '[data-forgo=\"child-of-root\"]'\n        ).length,\n        2,\n        \"Root element should have both appended elements\"\n      );\n    });\n\n    it(\"preserves children inserted with DOM APIs that are appended after of managed children\", async () => {\n      const { Component, state } = componentFactory();\n\n      await run((env) => (\n        <Component insertionPosition={\"last\" as const} {...env} />\n      ));\n\n      should.equal(\n        state.rootElement.value!.querySelectorAll(\n          '[data-forgo=\"child-of-root\"]'\n        ).length,\n        1,\n        \"Root element should have the appended element\"\n      );\n\n      state.buttonElement.value!.click();\n\n      should.equal(\n        state.rootElement.value!.querySelectorAll(\n          '[data-forgo=\"child-of-root\"]'\n        ).length,\n        2,\n        \"Root element should have both appended elements\"\n      );\n    });\n\n    it(\"preserves children inserted with DOM APIs that inserted in the middle of managed children\", async () => {\n      const { Component, state } = componentFactory();\n\n      await run((env) => <Component insertionPosition={2} {...env} />);\n\n      should.equal(\n        state.rootElement.value!.querySelectorAll(\n          '[data-forgo=\"child-of-root\"]'\n        ).length,\n        1,\n        \"Root element should have the appended element\"\n      );\n\n      state.buttonElement.value!.click();\n\n      should.equal(\n        state.rootElement.value!.querySelectorAll(\n          '[data-forgo=\"child-of-root\"]'\n        ).length,\n        2,\n        \"Root element should have both appended elements\"\n      );\n    });\n\n    it(\"doesn't add attributes to unmanaged elements\", async () => {\n      const { Component, state } = componentFactory();\n\n      await run((env) => <Component insertionPosition={2} {...env} />);\n\n      const el = state.rootElement.value!.querySelector(\n        '[data-forgo=\"child-of-root\"]'\n      )!;\n\n      // Mutate the component to force it to interact with the unmanaged element\n      // we added after the first render\n      state.buttonElement.value!.click();\n\n      should.equal(Array.from(el.attributes).length, 1);\n      const [{ name: attrName, value: attrValue }] = Array.from(el.attributes);\n      should.deepEqual([attrName, attrValue], [\"data-forgo\", \"child-of-root\"]);\n    });\n\n    /**\n     * If we mess up the algorithm for ignoring unmanaged nodes, what happens is\n     * forgo identifies an unmanaged node as a replacement candidate for a\n     * managed node and syncs its attrs/children onto the unmanaged node. So\n     * here, we're basically checking that all elements mangaged by forgo still\n     * have the tag they're supposed to have. This works because our test\n     * component makes the unmanaged nodes a different tag name than any of the\n     * managed nodes.\n     */\n    it(\"doesn't convert an unmanaged element into a managed element\", async () => {\n      const { Component, state } = componentFactory();\n\n      await run((env) => <Component insertionPosition={2} {...env} />);\n\n      // Mutate the component to force it to interact with the unmanaged element\n      // we added after the first render\n      state.buttonElement.value!.click();\n\n      [state.buttonElement, state.pElement, state.subrootElement].forEach(\n        (el) => should.notEqual(el.value!.tagName, unmanagedNodeTagName)\n      );\n\n      // Same test as above, but pull the elements out of the live DOM. This\n      // covers if there's any case where Forgo mis-transforms the elements\n      // without updating the ref.\n      [\n        state.rootElement.value!.querySelector(\"#button\"),\n        state.rootElement.value!.querySelector(\"#p\"),\n        state.rootElement.value!.querySelector(\"#subRoot\"),\n      ].forEach((el) => {\n        should.exist(el);\n        should.notEqual(el!.tagName, unmanagedNodeTagName);\n      });\n    });\n\n    it(\"leaves managed nodes alone when an unmanaged node is removed from the DOM\", async () => {\n      const { Component, state } = componentFactory();\n\n      await run((env) => <Component insertionPosition={2} {...env} />);\n\n      // Sanity check that our tests are correctly identifying unmanaged nodes\n      // before testing their removal\n      should.notEqual(\n        state.rootElement.value!.querySelectorAll(\"[data-forgo]\").length,\n        0\n      );\n      // Mutate the component to force it to interact with the unmanaged element\n      // we added after the first render\n      state.buttonElement.value!.click();\n      state.remove();\n\n      // All unmanaged nodes are gone\n      should.equal(\n        state.rootElement.value!.querySelectorAll(\"[data-forgo]\").length,\n        0\n      );\n\n      // All managed nodes still exist\n      [\n        state.rootElement.value!.querySelector(\"#button\"),\n        state.rootElement.value!.querySelector(\"#p\"),\n        state.rootElement.value!.querySelector(\"#subRoot\"),\n      ].forEach((el) => {\n        should.exist(el);\n        should.notEqual(el!.tagName, unmanagedNodeTagName);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/rerenderChild/index.ts",
    "content": "import { JSDOM } from \"jsdom\";\nimport htmlFile from \"../htmlFile.js\";\nimport { parentCounter, renderAgain, run, runSharedNode } from \"./script.js\";\nimport should from \"should\";\n\nexport default function () {\n  describe(\"rerenders child\", () => {\n    it(\"rerenders child on child node\", async () => {\n      const dom = new JSDOM(htmlFile(), {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      });\n      const window = dom.window;\n\n      run(dom);\n\n      await new Promise<void>((resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve();\n        });\n      });\n\n      window.document.body.innerHTML.should.containEql(\"Parent counter is 1\");\n      window.document.body.innerHTML.should.containEql(\"Child counter is 1\");\n      renderAgain();\n      window.document.body.innerHTML.should.containEql(\"Parent counter is 1\");\n      window.document.body.innerHTML.should.containEql(\"Child counter is 2\");\n    });\n\n    it(\"rerenders child sharing parent node\", async () => {\n      const dom = new JSDOM(htmlFile(), {\n        runScripts: \"outside-only\",\n        resources: \"usable\",\n      });\n      const window = dom.window;\n\n      runSharedNode(dom);\n\n      await new Promise<void>((resolve) => {\n        window.addEventListener(\"load\", () => {\n          resolve();\n        });\n      });\n\n      should.equal(parentCounter, 1);\n      window.document.body.innerHTML.should.containEql(\"Child counter is 1\");\n      renderAgain();\n      should.equal(parentCounter, 1);\n      window.document.body.innerHTML.should.containEql(\"Child counter is 2\");\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/rerenderChild/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nexport let parentCounter = 0;\nexport let childCounter = 0;\n\nlet component: forgo.Component<{}>;\nexport function renderAgain() {\n  component.update();\n}\n\nconst Parent = () => {\n  parentCounter = 0;\n\n  return new forgo.Component({\n    render() {\n      parentCounter++;\n      return (\n        <div>\n          <p>Parent counter is {parentCounter}</p>\n          <Child />\n        </div>\n      );\n    },\n  });\n};\n\nconst ParentWithSharedNode = () => {\n  parentCounter = 0;\n\n  return new forgo.Component({\n    render() {\n      parentCounter++;\n      return <Child />;\n    },\n  });\n};\n\nconst Child = () => {\n  childCounter = 0;\n\n  component = new forgo.Component({\n    render() {\n      childCounter++;\n      return (\n        <div>\n          <p>Child counter is {childCounter}</p>\n        </div>\n      );\n    },\n  });\n  return component;\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<Parent />, document.getElementById(\"root\"));\n  });\n}\n\nexport function runSharedNode(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<ParentWithSharedNode />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/rerenderMayChangeRootNode.tsx",
    "content": "import should from \"should\";\n\nimport * as forgo from \"../index.js\";\nimport { run } from \"./componentRunner.js\";\n\nfunction componentFactory() {\n  const state: {\n    component: forgo.Component<{}> | null;\n  } = { component: null };\n\n  const Parent1 = () => {\n    return new forgo.Component({\n      render() {\n        return <Parent2 />;\n      },\n    });\n  };\n\n  const Parent2 = () => {\n    return new forgo.Component({\n      render() {\n        return <Child />;\n      },\n    });\n  };\n\n  const Child = () => {\n    let counter = 0;\n    state.component = new forgo.Component({\n      render() {\n        counter++;\n        return counter === 1 ? (\n          <>\n            <div id=\"node1\">This is a child node.</div>\n            <div key=\"2\" id=\"node2\">\n              This is a child node.\n            </div>\n          </>\n        ) : (\n          <>\n            <div key=\"2\" id=\"node2\">\n              This is a child node.\n            </div>\n          </>\n        );\n      },\n    });\n    return state.component;\n  };\n\n  return {\n    Component: Parent1,\n    state,\n  };\n}\n\nexport default function () {\n  it(\"rerender may change root node\", async () => {\n    const { Component, state } = componentFactory();\n    const { document } = await run(() => <Component />);\n\n    const node1FirstPass = document.getElementById(\"node1\");\n    const node2FirstPass = document.getElementById(\"node2\");\n    const stateFirstPass = forgo.getForgoState(\n      node2FirstPass as ChildNode\n    ) as forgo.NodeAttachedState;\n    should.equal(\n      stateFirstPass.components[0].component.__internal.element.node,\n      node1FirstPass\n    );\n    should.equal(\n      stateFirstPass.components[1].component.__internal.element.node,\n      node1FirstPass\n    );\n    should.equal(\n      stateFirstPass.components[2].component.__internal.element.node,\n      node1FirstPass\n    );\n\n    state.component!.update();\n\n    const node2SecondPass = document.getElementById(\"node2\");\n    const stateSecondPass = forgo.getForgoState(\n      node2SecondPass as ChildNode\n    ) as forgo.NodeAttachedState;\n    should.equal(\n      stateSecondPass.components[0].component.__internal.element.node,\n      node2SecondPass\n    );\n    should.equal(\n      stateSecondPass.components[1].component.__internal.element.node,\n      node2SecondPass\n    );\n    should.equal(\n      stateSecondPass.components[2].component.__internal.element.node,\n      node2SecondPass\n    );\n  });\n}\n"
  },
  {
    "path": "src/test/rerenderMayChangeRootNodeOnParents.tsx",
    "content": "import should from \"should\";\n\nimport * as forgo from \"../index.js\";\nimport { run } from \"./componentRunner.js\";\n\nfunction componentFactory() {\n  const state: {\n    component: forgo.Component<{}> | null;\n  } = { component: null };\n\n  const Parent = () => {\n    state.component = new forgo.Component({\n      render() {\n        return <Child />;\n      },\n    });\n    return state.component;\n  };\n\n  const Child = () => {\n    let counter = 0;\n    return new forgo.Component({\n      render() {\n        counter++;\n        return counter === 1 ? (\n          <div id=\"node1\">This is a child node.</div>\n        ) : (\n          <p id=\"node2\">This is a child node.</p>\n        );\n      },\n    });\n  };\n\n  return {\n    Component: Parent,\n    state,\n  };\n}\n\nexport default function () {\n  it(\"rerender may change root node on parents\", async () => {\n    const { Component, state } = componentFactory();\n    await run(() => <Component />);\n\n    const oldId: string = (state.component!.__internal.element.node as Element)\n      .id;\n    should.equal(oldId, \"node1\");\n\n    state.component!.update();\n\n    const newId: string = (state.component!.__internal.element.node as Element)\n      .id;\n    should.equal(newId, \"node2\");\n  });\n}\n"
  },
  {
    "path": "src/test/rerenderMayUnmountParents.tsx",
    "content": "import should from \"should\";\n\nimport * as forgo from \"../index.js\";\nimport { run } from \"./componentRunner.js\";\n\nfunction componentFactory() {\n  const state: {\n    component: forgo.Component<{}> | null;\n    parent1Unmounted: boolean;\n    parent2Unmounted: boolean;\n    childUnmounted: boolean;\n  } = {\n    component: null,\n    parent1Unmounted: false,\n    parent2Unmounted: false,\n    childUnmounted: false,\n  };\n\n  const Parent1 = () => {\n    const component = new forgo.Component({\n      render() {\n        return <Parent2 />;\n      },\n    });\n    component.unmount(() => {\n      state.parent1Unmounted = true;\n    });\n    return component;\n  };\n\n  const Parent2 = () => {\n    const component = new forgo.Component({\n      render() {\n        return <Child />;\n      },\n    });\n    component.unmount(() => {\n      state.parent2Unmounted = true;\n    });\n    return component;\n  };\n\n  const Child = () => {\n    let counter = 0;\n    state.component = new forgo.Component({\n      render() {\n        counter++;\n        return counter === 1 ? <div>This is a child node.</div> : <></>;\n      },\n    });\n    state.component.unmount(() => {\n      state.childUnmounted = true;\n    });\n    return state.component;\n  };\n\n  return {\n    Component: Parent1,\n    state,\n  };\n}\n\nexport default function () {\n  it(\"rerender may unmount parents\", async () => {\n    const { Component, state } = componentFactory();\n    await run(() => <Component />);\n\n    state.component!.update();\n\n    should.equal(state.parent1Unmounted, true);\n    should.equal(state.parent2Unmounted, true);\n    should.equal(state.childUnmounted, true);\n  });\n}\n"
  },
  {
    "path": "src/test/rootElementChangeDoesNotUnmount.tsx",
    "content": "import * as forgo from \"../index.js\";\nimport { run } from \"./componentRunner.js\";\n\nfunction componentFactory() {\n  const state = {\n    unmountCount: 0,\n    renderCount: 0,\n    component: null as forgo.Component<{}> | null,\n  };\n\n  const TestComponent = () => {\n    const Child = () => {\n      const component = new forgo.Component({\n        render() {\n          state.renderCount++;\n          if (state.renderCount % 2 === 0) {\n            return <div>This is a div</div>;\n          } else {\n            return <p>But this is a paragraph</p>;\n          }\n        },\n      });\n      component.unmount(() => {\n        state.unmountCount++;\n      });\n      return component;\n    };\n\n    const component = new forgo.Component({\n      render() {\n        return (\n          <section>\n            <Child />\n          </section>\n        );\n      },\n    });\n    state.component = component;\n    return component;\n  };\n\n  return {\n    TestComponent,\n    state,\n  };\n}\n\nexport default function () {\n  describe(\"root element changes\", () => {\n    it(\"does not unmount\", async () => {\n      const { TestComponent, state } = componentFactory();\n      await run(() => <TestComponent />);\n\n      state.component!.update();\n      state.component!.update();\n      state.component!.update();\n      state.component!.update();\n      state.component!.update();\n\n      state.renderCount.should.equal(6);\n      state.unmountCount.should.equal(0);\n    });\n  });\n}\n"
  },
  {
    "path": "src/test/shouldUpdate.tsx",
    "content": "import * as forgo from \"../index.js\";\nimport { run } from \"./componentRunner.js\";\n\nfunction componentFactory() {\n  const state: {\n    component: forgo.Component<{}> | null;\n  } = { component: null };\n\n  const TestComponent = () => {\n    let counter = 0;\n\n    state.component = new forgo.Component({\n      render() {\n        counter++;\n        return <div>Counter is {counter}</div>;\n      },\n    });\n    state.component.shouldUpdate(() => {\n      return false;\n    });\n    return state.component;\n  };\n\n  return {\n    Component: TestComponent,\n    state,\n  };\n}\n\nexport default function () {\n  it(\"skips render if shouldUpdate() returns false\", async () => {\n    const { Component, state } = componentFactory();\n    const { document } = await run(() => <Component />);\n\n    state.component!.update();\n    state.component!.update();\n    state.component!.update();\n\n    document.body.innerHTML.should.containEql(\"Counter is 1\");\n  });\n}\n"
  },
  {
    "path": "src/test/ssr-simple.tsx",
    "content": "import { JSDOM } from \"jsdom\";\n\nimport * as forgo from \"../index.js\";\nimport htmlFile from \"./htmlFile.js\";\nimport { run } from \"./componentRunner.js\";\n\nfunction componentFactory() {\n  const state: {} = {};\n\n  interface GreetProps {\n    text: string;\n  }\n  const Greet = (_props: GreetProps) => {\n    return new forgo.Component<GreetProps>({\n      render(props) {\n        return <p>{props.text}</p>;\n      },\n    });\n  };\n\n  const Parent = () => {\n    return new forgo.Component({\n      render() {\n        return (\n          <div>\n            <Greet text=\"Hello2\" />\n            <Greet text=\"World2\" />\n          </div>\n        );\n      },\n    });\n  };\n\n  return {\n    Component: Parent,\n    state,\n  };\n}\n\nexport default function () {\n  it(\"simple server side rendering\", async () => {\n    const dom = new JSDOM(htmlFile(\"<div><p>Hello1</p><p>World1</p></div>\"), {\n      runScripts: \"outside-only\",\n      resources: \"usable\",\n    });\n\n    const { Component } = componentFactory();\n    const { document } = await run(() => <Component />, dom);\n\n    document.body.innerHTML.should.not.containEql(\"World1\");\n    document.body.innerHTML.should.containEql(\"World2\");\n  });\n}\n"
  },
  {
    "path": "src/test/test.ts",
    "content": "import sourceMapSupport from \"source-map-support\";\nsourceMapSupport.install();\n\nimport mount from \"./mount/index.js\";\nimport boundary from \"./boundary/index.js\";\nimport passProps from \"./passProps/index.js\";\nimport elementRef from \"./elementRef/index.js\";\nimport rerender from \"./rerender.js\";\nimport hydrate from \"./hydrate/index.js\";\nimport componentUnmount from \"./componentUnmount.js\";\nimport componentMount from \"./componentMount.js\";\nimport nodeState from \"./nodeState/index.js\";\nimport replaceByKey from \"./replaceByKey/index.js\";\nimport clearsOldProps from \"./clearsOldProps/index.js\";\nimport shouldUpdate from \"./shouldUpdate.js\";\nimport renderPrimitives from \"./renderPrimitives/index.js\";\nimport assertIsComponent from \"./assertIsComponent/index.js\";\nimport rendersArraysInChildren from \"./rendersArraysInChildren/index.js\";\nimport rerenderChild from \"./rerenderChild/index.js\";\nimport afterRender from \"./afterRender/index.js\";\nimport propsChanges from \"./propsChanges/index.js\";\nimport componentFragment from \"./componentFragment/index.js\";\nimport mountRunsOnceWhenRenderingFragment from \"./mountRunsOnceWhenRenderingFragment/index.js\";\nimport mountRunsOnceWhenChildRendersFragment from \"./mountRunsOnceWhenChildRendersFragment/index.js\";\nimport fragmentUnmountRunsOnce from \"./fragmentUnmountRunsOnce/index.js\";\nimport replacingFragmentWithNodeWontUnmount from \"./replacingFragmentWithNodeWontUnmount/index.js\";\nimport childWithFragmentUnmounts from \"./childWithFragmentUnmounts/index.js\";\nimport rerenderMayUnmountParents from \"./rerenderMayUnmountParents.js\";\nimport rerenderMayChangeRootNode from \"./rerenderMayChangeRootNode.js\";\nimport dangerouslySetInnerHTML from \"./dangerouslySetInnerHTML/index.js\";\nimport css from \"./css/index.js\";\nimport inheritedCustomElement from \"./inheritedCustomElement/index.js\";\nimport fragmentOverwriteDoesNotUnmount from \"./fragmentOverwriteDoesNotUnmount/index.js\";\nimport ssrSimple from \"./ssr-simple.js\";\nimport componentKeepsStateWhenReordered from \"./componentKeepsStateWhenReordered/index.js\";\nimport elementKeepsStateWhenReordered from \"./elementKeepsStateWhenReordered/index.js\";\nimport unmountsParentWhenNodeIsNull from \"./unmountsParentWhenNodeIsNull.js\";\nimport rerenderMayChangeRootNodeOnParents from \"./rerenderMayChangeRootNodeOnParents.js\";\nimport keyedFragmentsPreserveChildStates from \"./keyedFragmentsPreserveChildStates/index.js\";\nimport rootElementChangeDoesNotUnmount from \"./rootElementChangeDoesNotUnmount.js\";\nimport fragmentMountTiming from \"./fragmentMountEvent.js\";\nimport componentApi from \"./componentApi.js\";\n\nmount();\nboundary();\npassProps();\nelementRef();\nrerender();\nhydrate();\ncomponentMount();\ncomponentUnmount();\nnodeState();\nreplaceByKey();\nclearsOldProps();\nshouldUpdate();\nrenderPrimitives();\nassertIsComponent();\nrendersArraysInChildren();\nrerenderChild();\nafterRender();\npropsChanges();\ncomponentFragment();\nmountRunsOnceWhenRenderingFragment();\nmountRunsOnceWhenChildRendersFragment();\nfragmentUnmountRunsOnce();\nreplacingFragmentWithNodeWontUnmount();\nchildWithFragmentUnmounts();\nrerenderMayUnmountParents();\nrerenderMayChangeRootNode();\nrerenderMayChangeRootNodeOnParents();\ndangerouslySetInnerHTML();\ncss();\ninheritedCustomElement();\nfragmentOverwriteDoesNotUnmount();\nssrSimple();\ncomponentKeepsStateWhenReordered();\nelementKeepsStateWhenReordered();\nunmountsParentWhenNodeIsNull();\nkeyedFragmentsPreserveChildStates();\nrootElementChangeDoesNotUnmount();\nfragmentMountTiming();\ncomponentApi();\n"
  },
  {
    "path": "src/test/unmountsNonTopLevelParentWhenNodeIsNull/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nlet isFirstRender = true;\n\nexport let hasUnmounted = false;\n\nlet component: forgo.Component<{}>;\n\nexport function renderAgain() {\n  component.update();\n}\n\nconst Child = () => {\n  component = new forgo.Component({\n    render() {\n      if (isFirstRender) {\n        isFirstRender = false;\n        return <div>Hello, world</div>;\n      } else {\n        return null;\n      }\n    },\n  });\n  component.unmount(() => {\n    hasUnmounted = true;\n  });\n  return component;\n};\n\nconst Parent = () => {\n  return new forgo.Component({\n    render() {\n      return (\n        <div>\n          <Child />\n        </div>\n      );\n    },\n  });\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<Parent />, window.document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/unmountsParentWhenNodeIsNull/script.tsx",
    "content": "import * as forgo from \"../../index.js\";\nimport { DOMWindow, JSDOM } from \"jsdom\";\nimport { mount, setCustomEnv } from \"../../index.js\";\n\nlet window: DOMWindow;\nlet document: Document;\n\nlet isFirstRender = true;\n\nexport let hasUnmounted = false;\n\nlet component: forgo.Component<{}>;\nexport function renderAgain() {\n  component.update();\n}\n\nconst Parent = () => {\n  component = new forgo.Component({\n    render() {\n      if (isFirstRender) {\n        isFirstRender = false;\n        return <div>Hello, world</div>;\n      } else {\n        return null;\n      }\n    },\n  });\n  component.unmount(() => {\n    hasUnmounted = true;\n  });\n  return component;\n};\n\nexport function run(dom: JSDOM) {\n  window = dom.window;\n  document = window.document;\n  setCustomEnv({ window, document });\n\n  window.addEventListener(\"load\", () => {\n    mount(<Parent />, window.document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "src/test/unmountsParentWhenNodeIsNull.tsx",
    "content": "import should from \"should\";\n\nimport * as forgo from \"../index.js\";\nimport { run } from \"./componentRunner.js\";\n\nconst componentFactory = () => {\n  const state: {\n    shouldRenderNull: boolean;\n    internalState: string | null;\n    renderedElement?: ChildNode;\n    update: () => void;\n    hasMounted: boolean;\n    hasUnmounted: boolean;\n  } = {\n    shouldRenderNull: true,\n    internalState: null,\n    hasMounted: false,\n    hasUnmounted: false,\n    update: () => undefined,\n  };\n\n  const Component = () => {\n    // We want to reassign this inside the component ctor closure to test that\n    // the component doesn't get recreated when it stops rendering null\n    state.internalState = Math.random().toString();\n\n    const component = new forgo.Component({\n      render() {\n        if (state.shouldRenderNull) {\n          return null;\n        } else {\n          return <div>Internal state is {state.internalState}</div>;\n        }\n      },\n    });\n    state.update = component.update.bind(component);\n    component.mount(() => {\n      state.hasMounted = true;\n      component.unmount(() => (state.hasUnmounted = true));\n    });\n    component.afterRender((_props, _previousNode, component) => {\n      state.renderedElement = component.__internal.element.node!;\n    });\n\n    return component;\n  };\n  return { state, Component };\n};\n\nexport default function () {\n  it(\"does not unmount parent when render returns null\", async () => {\n    const { Component, state } = componentFactory();\n\n    await run(() => <Component />);\n\n    // Make sure that the first render mounts the component, even if it renders\n    // null\n    should.equal(state.hasMounted, true);\n    should.equal(state.hasUnmounted, false);\n    should.equal(state.renderedElement!.nodeType, 8);\n\n    const internalState = state.internalState;\n    // Sanity checks for rendering the randomized state\n    should.equal(typeof internalState, \"string\");\n    should.notEqual(internalState, \"\");\n\n    state.shouldRenderNull = true;\n    state.update();\n    should.equal(state.hasUnmounted, false);\n    should.equal(state.renderedElement!.nodeType, 8);\n\n    state.shouldRenderNull = false;\n    state.update();\n    should.equal(state.hasUnmounted, false);\n    should.equal(state.renderedElement!.nodeType, 1);\n    const newInternalState = state.internalState;\n    should.equal(newInternalState, internalState);\n  });\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"allowJs\": false,\n    \"target\": \"ES2015\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"node\",\n    \"allowSyntheticDefaultImports\": true,\n    \"lib\": [\"ES2020\", \"DOM\"],\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"jsx\": \"react\",\n    \"jsxFactory\": \"forgo.createElement\",\n    \"jsxFragmentFactory\": \"forgo.Fragment\",\n    \"declaration\": true,\n    // esbuild recommends enabling this to help enforce throwing errors in\n    // scenarios where esbuild and tsc's behavior would differ\n    //\n    // https://esbuild.github.io/content-types/#isolated-modules\n    \"isolatedModules\": true\n  },\n  \"include\": [\"./src/**/*\"]\n}\n"
  }
]