Full Code of subsecondtdd/todo-subsecond for AI

master 746a6a40a202 cached
53 files
30.8 KB
9.3k tokens
77 symbols
1 requests
Download .txt
Repository: subsecondtdd/todo-subsecond
Branch: master
Commit: 746a6a40a202
Files: 53
Total size: 30.8 KB

Directory structure:
gitextract_shujpsec/

├── .circleci/
│   └── config.yml
├── .gitignore
├── LICENSE
├── Procfile
├── README.md
├── cucumber
├── cucumber-electron
├── cucumber.js
├── features/
│   ├── run/
│   │   ├── all
│   │   ├── all.cmd
│   │   ├── browserstack-memory
│   │   ├── browserstack-memory.cmd
│   │   ├── database
│   │   ├── database.cmd
│   │   ├── dom-http-memory
│   │   ├── dom-http-memory.cmd
│   │   ├── dom-memory
│   │   ├── dom-memory.cmd
│   │   ├── http-memory
│   │   ├── http-memory.cmd
│   │   ├── memory
│   │   ├── memory.cmd
│   │   ├── webdriver-http-database
│   │   ├── webdriver-http-database.cmd
│   │   ├── webdriver-memory
│   │   └── webdriver-memory.cmd
│   ├── step_definitions/
│   │   └── todo_steps.js
│   ├── support/
│   │   ├── assemblies/
│   │   │   ├── browserstack-memory.js
│   │   │   ├── database.js
│   │   │   ├── dom-http-memory.js
│   │   │   ├── dom-memory.js
│   │   │   ├── http-memory.js
│   │   │   ├── memory.js
│   │   │   ├── webdriver-http-database.js
│   │   │   └── webdriver-memory.js
│   │   ├── parameter_types.js
│   │   └── world.js
│   └── todo.feature
├── lib/
│   ├── client/
│   │   ├── BrowserApp.js
│   │   ├── HttpTodoList.js
│   │   └── client.js
│   └── server/
│       ├── DatabaseTodoList.js
│       ├── WebApp.js
│       ├── asyncExpress.js
│       └── start.js
├── package.json
├── public/
│   ├── index.html
│   └── todo.css
├── test/
│   └── MemoryTodoListTest.js
└── test_support/
    ├── BrowserStackTodoList.js
    ├── DomTodoList.js
    ├── MemoryTodoList.js
    └── WebDriverTodoList.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .circleci/config.yml
================================================
version: 2
jobs:
  build:
    docker:
      - image: circleci/node:8-browsers
        environment:
          DATABASE_URL: sqlite:./todo-subsecond.sqlite
    working_directory: ~/repo
    steps:
      - run:
          name: check version
          command: |
            node --version
            yarn --version
      - checkout
      - restore_cache:
          keys:
          - todo-subsecond-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }}
      - run:
          name: install dependencies
          command: yarn install
      - save_cache:
          paths:
            - node_modules
          key: todo-subsecond-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }}
      - run: yarn test


================================================
FILE: .gitignore
================================================
node_modules
local.log
browserstack.err
.DS_Store
todo-subsecond.sqlite


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) Sub-Second TDD Organization

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: Procfile
================================================
web: node lib/server/start.js


================================================
FILE: README.md
================================================
# TODO Subsecond

[![CircleCI](https://circleci.com/gh/subsecondtdd/todo-subsecond.svg?style=svg)](https://circleci.com/gh/subsecondtdd/todo-subsecond)

This is a tiny application with full-stack acceptance tests that can run in milliseconds. The purpose is to illustrate
the essential techniques to achieve this in any system.

Through a series of exercises you'll get familiar with a particular way of doing TDD/BDD:

1. Small increments. Work at a single layer at a time.
2. Fast (sub-second) feedback loop.
3. UI-agnostic scenarios. Describe system behaviour rather than implemention.
4. Testing through the UI without the typical drawbacks (slow, brittle).

Fast and stable full-stack tests that go through the UI are possible when the following criteria are satisfied:

* No I/O whatsoever
  * No I/O between your test and a browser automation tool like Selenium WebDriver
  * No I/O between Selenium WebDriver and a browser
  * No I/O between the browser application and the server
  * No I/O between the server and a database
* Clearly defined interfaces/contracts that allow swapping out the slow I/O bound components above
* UI and server run in the same process
  * This is possible thanks to [cucumber-electron](https://github.com/cucumber/cucumber-electron)
* Tests/Scenarios are decoupled from the UI, allowing the UI to change with minimal impact on tests

## Credits and inspiration

The techniques illustrated by this application are inspired by:

* [Nat Pryce's "Having Our Cake and Eating it](https://youtu.be/Fk4rCn4YLLU) - slides [here](https://speakerdeck.com/npryce/having-our-cake-and-eating-it-1)
* [Jan Molak's "Testing modern web apps. At scale."](http://agileonthebeach.com/testing-modern-web-apps-scale-jan-molak-software-delivery-2017/)
* Josh's colleagues at [Featurist](https://www.featurist.co.uk/)
* Aslak's colleagues at [Cucumber Ltd](https://cucumber.io)

## Install Software

* [Node.js 8.x or higher](https://nodejs.org/en/download/current/)

## Install dependencies

    npm install

## Create a database

Although this repository is intended as a demonstration of a design pattern, we want it to be as realistic as possible. So ideally you should use a database that you might use in production, like the [instance we deployed with a postgres database](https://todo-subsecond.herokuapp.com).

If you have Postgres installed you can use that. Otherwise you can use SQlite (this is simpler as it does not require a separate installation):

### Postgres

    createdb todo-subsecond

### SQlite

    # Linux/OSX:
    export DATABASE_URL=sqlite:./todo-subsecond.sqlite

    # Windows:
    set DATABASE_URL=sqlite:./todo-subsecond.sqlite

## Run tests

Run all the tests in a single command:

    # Linux/OSX:
    features/run/all

    # Windows:
    features\run\all

Or just one assembly at a time (we'll explain assemblies further down):

    # Linux/OSX:
    features/run/memory

    # Windows:
    features\run\memory

### Change browser

The `webdriver-*` assemblies will use Chrome by default. You can change to another browser if you want:

    # Linux/OSX:
    brew install geckodriver
    export SELENIUM_BROWSER=firefox

    # Windows:
    set SELENIUM_BROWSER=firefox

## Start the server

The application is a small web application that renders a simple single-page application. Fire it up:

    npm start

Open `http://localhost:8666` in your browser and add a couple of todos.

![screenshot](docs/screenshot.png)

Check one of them and refresh the page. As you'll see, the application doesn't remember that a todo is marked as done.

## Implement a new feature

We're going to improve the application to make it remember the state of todos.

Uncomment the three steps in `features/todo.feature` and run `features/run/all` again. Cucumber will tell you that
two steps are undefined. Copy the first snippet and paste it into `features/step_definitions/todo_steps.js`.

Modify it so it looks like this:

```javascript
When('I mark the {ordinal} todo as done', async function (index) {
  const todoList = await this.actionTodoList()
  await todoList.markAsDone(index)
})
```

Run Cucumber again. This time you'll get an error. Open `test_support/MemoryTodoList.js` and implement a new
`markAsDone` method.

Run `features/run/all` again until you've managed to get the 2nd step to pass.

Now, paste the snippet for the last undefined step and modify it:

```javascript
Then('the {ordinal} todo should be marked as done', async function (index) {
  const todoList = await this.outcomeTodoList()
  const todos = await todoList.getTodos()
  assert.equal(todos[index].done, true)
})
```

Run `features/run/all` again until all steps pass. When they do, Cucumber will run the same scenario in a new configuration,
`dom-memory`. This will fail. Now you need to make changes to `DomTodoList.js`.

Continue until everything is green. Every time you have a failing step you can make small increments in a single layer
of the application. Eventually all scenarios should pass.

When they do, you can do a final manual test just to give yourself confidence that everything works as expected.

That's it - subsecond TDD with acceptance tests that run in milliseconds while you're developing. You shouldn't have to
rely on the slow acceptance tests (`webdriver-memory` and `webdriver-http-database`) while you're implementing the new
functionality. You should only run them at the end to give you full confidence, and they should just pass without
requiring you to make any changes.

## Assemblies

When you look at `features/todo.feature` and `features/step_definitions/todo_steps.js` you'll notice that the scenarios
and step definitions make no assumptions about how to interact with the application.

We're interacting with the application *through a contract*, or an *interface* - `addTodo` and `getTodos`. Think of your
scenarios as a lego-like block that can be connected to another block that satisfies this contract:

![lego](docs/test.png)

In a statically typed language such as Java, C# or TypeScript, this would be an interface. With JavaScript it's just
*implicitly* defined.

Several parts of the application as well as additional test code can implement this interface/contract:

![lego](docs/lego.png)

There are three kinds of blocks:

* Green blocks represent *test code*
* Blue blocks represent *application code*
* Pink blocks represent *infrastructure*

Tests can be connected to the top of any stack of blocks and test different assemblies
of the application.

A tall stack of blocks gives more confidence at the cost of speed.

The speed doesn't depend directly on the number of blocks, but rather on the
amount of I/O happening *between* each block and the amount of CPU processing
happening *inside* each block.


================================================
FILE: cucumber
================================================
#!/usr/bin/env bash
node node_modules/.bin/cucumber-js "$@"


================================================
FILE: cucumber-electron
================================================
#!/usr/bin/env bash
node node_modules/.bin/cucumber-electron "$@"


================================================
FILE: cucumber.js
================================================
module.exports = {
  default: '--format-options \'{"snippetInterface": "promise"}\''
}


================================================
FILE: features/run/all
================================================
#!/usr/bin/env bash
features/run/memory && \
features/run/dom-memory && \
features/run/http-memory && \
features/run/dom-http-memory && \
features/run/database && \
features/run/webdriver-memory && \
features/run/webdriver-http-database && \
features/run/browserstack-memory


================================================
FILE: features/run/all.cmd
================================================
@echo off
call features\run\memory
if %errorlevel% neq 0 exit /b %errorlevel%
call features\run\dom-memory
if %errorlevel% neq 0 exit /b %errorlevel%
call features\run\http-memory
if %errorlevel% neq 0 exit /b %errorlevel%
call features\run\dom-http-memory
if %errorlevel% neq 0 exit /b %errorlevel%
call features\run\database
if %errorlevel% neq 0 exit /b %errorlevel%
call features\run\webdriver-memory
if %errorlevel% neq 0 exit /b %errorlevel%
call features\run\webdriver-http-database
if %errorlevel% neq 0 exit /b %errorlevel%
call features\run\browserstack-memory
if %errorlevel% neq 0 exit /b %errorlevel%


================================================
FILE: features/run/browserstack-memory
================================================
#!/usr/bin/env bash
if [[ -z "${BROWSERSTACK_USER}" ]]; then
  echo "set BROWSERSTACK_USER and BROWSERSTACK_KEY to run features on browserstack"
else
  CUCUMBER_ASSEMBLY=browserstack-memory node node_modules/.bin/cucumber-js "$@"
fi


================================================
FILE: features/run/browserstack-memory.cmd
================================================
@echo off
IF "%BROWSERSTACK_USER%"=="" (
  echo set BROWSERSTACK_USER and BROWSERSTACK_KEY to run features on browserstack
) ELSE (
  set CUCUMBER_ASSEMBLY=browserstack-memory
  node_modules\.bin\cucumber-js %*
)


================================================
FILE: features/run/database
================================================
#!/usr/bin/env bash
CUCUMBER_ASSEMBLY=database node node_modules/.bin/cucumber-js "$@"


================================================
FILE: features/run/database.cmd
================================================
@echo off
set CUCUMBER_ASSEMBLY=database
node_modules\.bin\cucumber-js %*


================================================
FILE: features/run/dom-http-memory
================================================
#!/usr/bin/env bash
CUCUMBER_ASSEMBLY=dom-http-memory node node_modules/.bin/cucumber-electron "$@"


================================================
FILE: features/run/dom-http-memory.cmd
================================================
@echo off
set CUCUMBER_ASSEMBLY=dom-http-memory
node_modules\.bin\cucumber-electron %*


================================================
FILE: features/run/dom-memory
================================================
#!/usr/bin/env bash
CUCUMBER_ASSEMBLY=dom-memory node node_modules/.bin/cucumber-electron "$@"


================================================
FILE: features/run/dom-memory.cmd
================================================
@echo off
set CUCUMBER_ASSEMBLY=dom-memory
node_modules\.bin\cucumber-electron %*


================================================
FILE: features/run/http-memory
================================================
#!/usr/bin/env bash
CUCUMBER_ASSEMBLY=http-memory node node_modules/.bin/cucumber-js "$@"


================================================
FILE: features/run/http-memory.cmd
================================================
@echo off
set CUCUMBER_ASSEMBLY=http-memory
node_modules\.bin\cucumber-js %*


================================================
FILE: features/run/memory
================================================
#!/usr/bin/env bash
CUCUMBER_ASSEMBLY=memory node node_modules/.bin/cucumber-js "$@"


================================================
FILE: features/run/memory.cmd
================================================
@echo off
set CUCUMBER_ASSEMBLY=memory
node_modules\.bin\cucumber-js %*


================================================
FILE: features/run/webdriver-http-database
================================================
#!/usr/bin/env bash
CUCUMBER_ASSEMBLY=webdriver-http-database node node_modules/.bin/cucumber-js "$@"


================================================
FILE: features/run/webdriver-http-database.cmd
================================================
@echo off
set CUCUMBER_ASSEMBLY=webdriver-http-database
node_modules\.bin\cucumber-js %*


================================================
FILE: features/run/webdriver-memory
================================================
#!/usr/bin/env bash
CUCUMBER_ASSEMBLY=webdriver-memory node node_modules/.bin/cucumber-js "$@"


================================================
FILE: features/run/webdriver-memory.cmd
================================================
@echo off
set CUCUMBER_ASSEMBLY=webdriver-memory
node_modules\.bin\cucumber-js %*


================================================
FILE: features/step_definitions/todo_steps.js
================================================
const assert = require('assert')
const { Given, When, Then, setDefaultTimeout } = require('cucumber')

setDefaultTimeout(60000)

Given('there is/are already {int} todo(s)', async function(count) {
  const todoList = await this.contextTodoList()
  for (let i = 0; i < count; i++)
    await todoList.addTodo({ text: `Todo ${i}` })
})

When('I add {string}', async function(text) {
  const todoList = await this.actionTodoList()
  await todoList.addTodo({ text })
})

Then('the text of the {ordinal} todo should be {string}', async function(index, text) {
  const todoList = await this.outcomeTodoList()
  const todos = await todoList.getTodos()
  assert.equal(todos[index].text, text)
})


================================================
FILE: features/support/assemblies/browserstack-memory.js
================================================
const BrowserStackTodoList = require('../../../test_support/BrowserStackTodoList')
const MemoryTodoList = require('../../../test_support/MemoryTodoList')
const WebApp = require('../../../lib/server/WebApp')

module.exports = class BrowserStackMemoryAssembly {
  async start () {
    this._memoryTodoList = new MemoryTodoList()
    this._webApp = new WebApp({ todoList: this._memoryTodoList, serveClient: true })
    const port = await this._webApp.listen(0)
    this._browserStackTodoList = new BrowserStackTodoList(`http://localhost:${port}`)
    await this._browserStackTodoList.start()
  }

  async stop () {
    await this._browserStackTodoList.stop()
    await this._webApp.stop()
  }

  async contextTodoList() {
    return this._memoryTodoList
  }

  async actionTodoList() {
    return this._browserStackTodoList
  }

  async outcomeTodoList() {
    return this._browserStackTodoList
  }
}


================================================
FILE: features/support/assemblies/database.js
================================================
const DatabaseTodoList = require('../../../lib/server/DatabaseTodoList')

module.exports = class DatabaseAssembly {
  async start () {
    this._databaseTodoList = new DatabaseTodoList()
    await this._databaseTodoList.start(true)
  }

  async stop() {
    await this._databaseTodoList.stop()
  }

  async contextTodoList() {
    return this._databaseTodoList
  }

  async actionTodoList() {
    return this._databaseTodoList
  }

  async outcomeTodoList() {
    return this._databaseTodoList
  }
}


================================================
FILE: features/support/assemblies/dom-http-memory.js
================================================
const fs = require('fs')
const path = require('path')
const DomTodoList = require('../../../test_support/DomTodoList')
const MemoryTodoList = require('../../../test_support/MemoryTodoList')
const HttpTodoList = require('../../../lib/client/HttpTodoList')
const BrowserApp = require('../../../lib/client/BrowserApp')
const WebApp = require('../../../lib/server/WebApp')

module.exports = class DomHttpMemoryAssembly {
  async start () {
    this._memoryTodoList = new MemoryTodoList()
    this.webApp = new WebApp({ todoList: this._memoryTodoList, serveClient: false })
  }

  async stop() {}

  async contextTodoList() {
    return this._memoryTodoList
  }

  async actionTodoList() {
    return await this._makeDomTodoList()
  }

  async outcomeTodoList() {
    return await this._makeDomTodoList()
  }

  async _makeDomTodoList() {
    const publicIndexHtmlPath = path.join(__dirname, '..', '..', '..', 'public', 'index.html')
    const html = fs.readFileSync(publicIndexHtmlPath, 'utf-8')
    const domNode = document.createElement('div')
    domNode.innerHTML = html
    document.body.appendChild(domNode)
    await new BrowserApp({ domNode, todoList: await this._makeHttpTodoList() }).mount()
    return new DomTodoList(domNode)
  }

  async _makeHttpTodoList() {
    const port = await this.webApp.listen(0)
    return new HttpTodoList(`http://localhost:${port}`)
  }
}


================================================
FILE: features/support/assemblies/dom-memory.js
================================================
const fs = require('fs')
const path = require('path')
const DomTodoList = require('../../../test_support/DomTodoList')
const MemoryTodoList = require('../../../test_support/MemoryTodoList')
const BrowserApp = require('../../../lib/client/BrowserApp')

module.exports = class DomMemoryAssembly {
  async start () {
    this._memoryTodoList = new MemoryTodoList()
  }

  async stop() {}

  async contextTodoList() {
    return this._memoryTodoList
  }

  async actionTodoList() {
    return await this._makeDomTodoList()
  }

  async outcomeTodoList() {
    return await this._makeDomTodoList()
  }

  async _makeDomTodoList() {
    const publicIndexHtmlPath = path.join(__dirname, '..', '..', '..', 'public', 'index.html')
    const html = fs.readFileSync(publicIndexHtmlPath, 'utf-8')
    const domNode = document.createElement('div')
    domNode.innerHTML = html
    document.body.appendChild(domNode)
    await new BrowserApp({ domNode, todoList: this._memoryTodoList }).mount()
    return new DomTodoList(domNode)
  }
}


================================================
FILE: features/support/assemblies/http-memory.js
================================================
const MemoryTodoList = require('../../../test_support/MemoryTodoList')
const WebApp = require('../../../lib/server/WebApp')
const HttpTodoList = require('../../../lib/client/HttpTodoList')

module.exports = class HttpMemoryAssembly {
  async start () {
    this._memoryTodoList = new MemoryTodoList()
    this._webApp = new WebApp({ todoList: this._memoryTodoList, serveClient: false })
    const port = await this._webApp.listen(0)
    this._httpTodoList = new HttpTodoList(`http://localhost:${port}`)
  }

  async stop () {
    await this._webApp.stop()
  }

  async contextTodoList() {
    return this._memoryTodoList
  }

  async actionTodoList() {
    return this._httpTodoList
  }

  async outcomeTodoList() {
    return this._httpTodoList
  }
}


================================================
FILE: features/support/assemblies/memory.js
================================================
const MemoryTodoList = require('../../../test_support/MemoryTodoList')

module.exports = class MemoryAssembly {
  async start () {
    this._memoryTodoList = new MemoryTodoList()
  }

  async stop() {}

  async contextTodoList() {
    return this._memoryTodoList
  }

  async actionTodoList() {
    return this._memoryTodoList
  }

  async outcomeTodoList() {
    return this._memoryTodoList
  }
}


================================================
FILE: features/support/assemblies/webdriver-http-database.js
================================================
const WebDriverTodoList = require('../../../test_support/WebDriverTodoList')
const DatabaseTodoList = require('../../../lib/server/DatabaseTodoList')
const WebApp = require('../../../lib/server/WebApp')

module.exports = class WebDriverDatabaseAssembly {
  async start () {
    this._databaseTodoList = new DatabaseTodoList()
    await this._databaseTodoList.start(true)
    this._webApp = new WebApp({ todoList: this._databaseTodoList, serveClient: true })
    const port = await this._webApp.listen(0)
    this._webDriverTodoList = new WebDriverTodoList(`http://localhost:${port}`)
  }

  async stop () {
    await this._webDriverTodoList.stop()
    await this._webApp.stop()
  }

  async contextTodoList() {
    return this._databaseTodoList
  }

  async actionTodoList() {
    await this._webDriverTodoList.start()
    return this._webDriverTodoList
  }

  async outcomeTodoList() {
    return this._webDriverTodoList
  }
}


================================================
FILE: features/support/assemblies/webdriver-memory.js
================================================
const WebDriverTodoList = require('../../../test_support/WebDriverTodoList')
const MemoryTodoList = require('../../../test_support/MemoryTodoList')
const WebApp = require('../../../lib/server/WebApp')

module.exports = class WebDriverMemoryAssembly {
  async start () {
    this._memoryTodoList = new MemoryTodoList()
    this._webApp = new WebApp({ todoList: this._memoryTodoList, serveClient: true })
    const port = await this._webApp.listen(0)
    this._webDriverTodoList = new WebDriverTodoList(`http://localhost:${port}`)
  }

  async stop () {
    await this._webDriverTodoList.stop()
    await this._webApp.stop()
  }

  async contextTodoList() {
    return this._memoryTodoList
  }

  async actionTodoList() {
    await this._webDriverTodoList.start()
    return this._webDriverTodoList
  }

  async outcomeTodoList() {
    return this._webDriverTodoList
  }
}


================================================
FILE: features/support/parameter_types.js
================================================
const { defineParameterType } = require('cucumber')

defineParameterType({
  name: 'ordinal',
  regexp: /(\d+)(?:st|nd|rd|th)/,
  transformer(s) {
    return parseInt(s) - 1
  }
})

================================================
FILE: features/support/world.js
================================================
const { setWorldConstructor, Before, After } = require('cucumber')

const assemblyName = process.env.CUCUMBER_ASSEMBLY || 'memory'
console.log(`🥒 ${assemblyName}`)

const AssemblyModule = require(`./assemblies/${assemblyName}`)
const assembly = new AssemblyModule()

class TodoWorld {
  constructor() {
    this.contextTodoList = () => assembly.contextTodoList()
    this.actionTodoList = () => assembly.actionTodoList()
    this.outcomeTodoList = () => assembly.outcomeTodoList()
  }
}

setWorldConstructor(TodoWorld)

Before(async function() {
  await assembly.start()
})

After(async function() {
  await assembly.stop()
})


================================================
FILE: features/todo.feature
================================================
Feature: Todo

  Scenario: Create Todo
    Given there is already 1 todo
    When I add "get milk"
    Then the text of the 2nd todo should be "get milk"

  Scenario: Mark Todo Done
    # Given there are already 2 todos
    # When I mark the 2nd todo as done
    # Then the 2nd todo should be marked as done

  Scenario: Mark Todo Undone
  Scenario: Delete Todo


================================================
FILE: lib/client/BrowserApp.js
================================================
module.exports = class BrowserApp {
  constructor({ domNode, todoList }) {
    this._domNode = domNode
    this._todoList = todoList

    this._addTodoButton = this._domNode.querySelector('[aria-label="Add Todo"]')
    this._todoTextField = this._domNode.querySelector('[aria-label="New Todo Text"]')
    this._todos = this._domNode.querySelector('[aria-label="Todos"]')

    this._addTodoButton.addEventListener('click', event => {
      event.preventDefault()
      const text = this._todoTextField.value
      this.addTodo({ text })
        .then(() => {
          this._todoTextField.value = ''
        })
        .catch(err => console.error(err))
    })
  }

  async mount() {
    this.renderTodos(await this._todoList.getTodos())
  }

  async addTodo({ text }) {
    await this._todoList.addTodo({ text })

    const todos = await this._todoList.getTodos()
    await this.renderTodos(todos)

    this._domNode.dispatchEvent(
      new window.CustomEvent('todos:todo:added', {
        bubbles: true,
      })
    )
  }

  renderTodos(todos) {
    // Remove old todos
    while (this._todos.hasChildNodes()) {
      this._todos.removeChild(this._todos.lastChild)
    }

    const ol = document.createElement('ol')
    this._todos.appendChild(ol)

    // Render new ones
    for (let i = 0; i < todos.length; i++) {
      const todo = todos[i]
      const li = document.createElement('li')
      const label = document.createElement('label')
      const checkbox = document.createElement('input')
      checkbox.type = 'checkbox'
      const textNode = document.createTextNode(todo.text)

      label.appendChild(checkbox)
      label.appendChild(textNode)
      li.appendChild(label)
      ol.appendChild(li)
    }
  }
}


================================================
FILE: lib/client/HttpTodoList.js
================================================
module.exports = class HttpTodoList {
  constructor(baseUrl, fetch) {
    this._baseUrl = baseUrl
    this._fetch = fetch || require('node-fetch')
  }

  async addTodo({ text }) {
    const res = await this._fetch(`${this._baseUrl}/todos`, {
      method: 'post',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ text })
    })
    if (res.status !== 201)
      throw new Error('Failed to create TODO')
  }

  async getTodos() {
    const res = await this._fetch(`${this._baseUrl}/todos`)
    if (res.status !== 200)
      throw new Error('Failed to get TODOs')
    return res.json()
  }
}


================================================
FILE: lib/client/client.js
================================================
const BrowserApp = require('./BrowserApp')
const HttpTodoList = require('./HttpTodoList')

new BrowserApp({
  domNode: document.body,
  todoList: new HttpTodoList('', window.fetch.bind(window))
}).mount()


================================================
FILE: lib/server/DatabaseTodoList.js
================================================
const Sequelize = require('sequelize');

module.exports = class DatabaseTodoList {

  constructor() {
    const databaseUrl = process.env.DATABASE_URL || 'postgres://localhost/todo-subsecond'
    this._sequelize = new Sequelize(databaseUrl, { logging: false })

    this.Todo = this._sequelize.define('todo', {
      text: Sequelize.STRING,
      // Use Sequelize.BOOLEAN for the done field
    })
  }

  async start(truncate) {
    if (truncate) {
      await this._sequelize.sync({ force: true })
    }
  }

  async stop(truncate) {
    await this._sequelize.close()
  }

  async addTodo({ text }) {
    await this.Todo.create({ text })
  }

  async getTodos() {
    return this.Todo.findAll().map(record => ({
      text: record.text
    }))
  }

  // async markAsDone(index) {
  //   const todo = .....
  //   await todo.update({ done: true })
  // }
}


================================================
FILE: lib/server/WebApp.js
================================================
const http = require('http')
const express = require('express')
const bodyParser = require('body-parser')
const asyncExpress = require('./asyncExpress')

module.exports = class WebApp {
  constructor({ todoList, serveClient }) {
    this._todoList = todoList
    this._serveClient = serveClient
  }

  async buildApp() {
    const app = express()
    app.use(bodyParser.json())

    app.use(express.static('./public'))

    if (this._serveClient) {
      const browserify = require('browserify-middleware')
      app.get('/client.js', browserify(__dirname + '/../client/client.js'))
    }

    const asyncApp = asyncExpress(app)

    asyncApp.post('/todos', async (req, res) => {
      const { text } = req.body
      await this._todoList.addTodo({ text })
      res.status(201).end()
    })

    asyncApp.get('/todos', async (req, res) => {
      res.setHeader('Content-Type', 'application/json')
      const todos = await this._todoList.getTodos()
      res.status(200).end(JSON.stringify(todos))
    })

    return app
  }

  async listen(port) {
    const app = await this.buildApp()
    this._server = http.createServer(app)
    return new Promise((resolve, reject) => {
      this._server.listen(port, err => {
        if (err) return reject(err)
        resolve(this._server.address().port)
      })
    })
  }

  async stop() {
    await new Promise((resolve, reject) => {
      this._server.close(err => {
        if (err) return reject(err)
        resolve()
      })
    })
  }
}


================================================
FILE: lib/server/asyncExpress.js
================================================
const wrap = asyncFn => (req, res, next) => {
  asyncFn(req, res, next).catch(err => next(err))
}

module.exports = function(app) {
  return {
    delete: (path, fn) => app.delete(path, wrap(fn)),
    get: (path, fn) => app.get(path, wrap(fn)),
    post: (path, fn) => app.post(path, wrap(fn)),
    put: (path, fn) => app.put(path, wrap(fn)),
  }
}


================================================
FILE: lib/server/start.js
================================================
const WebApp = require('./WebApp')
const DatabaseTodoList = require('./DatabaseTodoList')

const port = process.env.PORT || 8666

const todoList = new DatabaseTodoList()

todoList.start()
  .then(() => new WebApp({ todoList, serveClient: true }).listen(port))
  .then(() => console.log(`http://localhost:${port}`))


================================================
FILE: package.json
================================================
{
  "name": "todo-subsecond",
  "version": "1.0.0",
  "main": "index.js",
  "contributors": [
    "Josh Chisholm <joshuachisholm@gmail.com>",
    "Aslak Hellesøy <aslak@cucumber.io>"
  ],
  "license": "MIT",
  "scripts": {
    "start": "node lib/server/start.js",
    "mocha": "mocha",
    "test": "features/run/all",
    "time": "time features/run/memory && time features/run/webdriver-http-database"
  },
  "dependencies": {
    "body-parser": "^1.19.0",
    "browserify": "^16.5.0",
    "browserify-middleware": "^8.1.1",
    "express": "^4.17.1",
    "pg": "^7.18.2",
    "pg-hstore": "^2.3.3",
    "sequelize": "^5.21.5",
    "sqlite3": "^4.1.1"
  },
  "devDependencies": {
    "browserstack-local": "^1.4.5",
    "cucumber": "^6.0.5",
    "cucumber-electron": "^2.7.0",
    "electron": "^8.1.1",
    "mocha": "^7.1.0",
    "node-fetch": "^2.6.0",
    "selenium-webdriver": "^4.0.0-alpha.7"
  }
}


================================================
FILE: public/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>todos</title>
    <link rel="stylesheet" href="/todo.css" />
  </head>
  <body>
    <form>
      <input type="text" aria-label="New Todo Text"  title="New Todo Text"/>
      <input type="submit" value="Add" aria-label="Add Todo" />
    </form>

    <div aria-label="Todos">
    </div>
    <script src="/client.js"></script>
  </body>
</html>


================================================
FILE: public/todo.css
================================================
* {
  box-sizing: border-box;
}
body {
  font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
  background-color: rgb(245, 245, 245);
  color: #333;
}
body::before {
  content: 'todos';
  display: block;
  text-align: center;
  padding: 20px;
  font-size: 100px;
  color: rgba(175, 47, 47, 0.3);
  font-weight: 100;
}
form {
  text-align: center;
  width: 600px;
  margin: auto;
  padding: 20px;
  background-color: #fff;
  border: 3px solid #efefef;
  border-bottom: none;
  position: relative;
}
input[type="checkbox"] {
  transform:scale(2, 2);
  position: absolute;
  left: 32px;
  top: 28px;
}
form input, form button, ol li label {
  font-size: 30px;
  font-weight: 400;
}
form input[type="text"], form input[type="submit"] {
  border: 2px solid #efefef;
  padding: 10px;
}

form input[type="text"] {
  width: 400px;
  text-indent: 20px;
}
form input[type="submit"] {
  width: 120px;
  margin-left: 5px;
}
ol {
  margin: 0;
  width: 600px;
  margin: auto;
  padding: 0;
  border: 3px solid #efefef;
  border-top: none;
}
ol li {
  list-style-type: none;
  border-top: 2px solid #efefef;
  position: relative;
}
ol li label {
  width: 100%;
  display: block;
  padding: 20px;
  background-color: #fff;
  color: #333;
  padding-left: 80px;
}
ol li label:hover {
  background-color: rgba(250, 250, 250, 0.15);
}
ol li input {
  font-size: 30px;
}


================================================
FILE: test/MemoryTodoListTest.js
================================================
const assert = require('assert')
const MemoryTodoList = require('../test_support/MemoryTodoList')

describe('MemoryTodoList', () => {
  it('stores and retrieves TODOs', async () => {
    const todoList = new MemoryTodoList()
    await todoList.addTodo({ text: 'Get milk' })
    const todos = await todoList.getTodos()
    assert.deepEqual(todos, [{text: 'Get milk'}])
  })
})

================================================
FILE: test_support/BrowserStackTodoList.js
================================================
const WebDriverTodoList = require('./WebDriverTodoList')
const browserstack = require('browserstack-local')

module.exports = class BrowserStackTodoList extends WebDriverTodoList {
  buildDriver(builder) {
    return builder
      .usingServer('http://hub-cloud.browserstack.com/wd/hub')
      .withCapabilities({
        'browserName': 'firefox',
        'browserstack.user': process.env.BROWSERSTACK_USER,
        'browserstack.key': process.env.BROWSERSTACK_KEY,
        'browserstack.local': 'true'
      })
  }

  async start() {
    this.browserstackLocal = new browserstack.Local()
    await new Promise((resolve, reject) => {
      this.browserstackLocal.start({ key: process.env.BROWSERSTACK_KEY }, () => {
        resolve()
      })
    })
    await super.start()
  }

  async stop() {
    await super.stop()
    await new Promise((resolve, reject) => {
      this.browserstackLocal.stop(() => {
        resolve()
      })
    })
  }
}


================================================
FILE: test_support/DomTodoList.js
================================================
module.exports = class DomTodoList {
  constructor(domNode) {
    this._domNode = domNode
  }

  async addTodo({ text }) {
    return new Promise(resolve => {
      this._domNode.addEventListener('todos:todo:added', () => {
        resolve()
      })

      this._domNode.querySelector('[aria-label="New Todo Text"]').value = text
      this._domNode.querySelector('[aria-label="Add Todo"]').click()
    })
  }

  async getTodos() {
    return [...this._domNode.querySelectorAll('[aria-label="Todos"] li label')].map(label => ({
      text: label.innerText
    }))
  }
}


================================================
FILE: test_support/MemoryTodoList.js
================================================
module.exports = class MemoryTodoList {
  constructor() {
    this._todos = []
  }

  async addTodo({ text }) {
    this._todos.push({ text })
  }

  async getTodos() {
    return this._todos
  }
}


================================================
FILE: test_support/WebDriverTodoList.js
================================================
const webdriver = require('selenium-webdriver')
const { By, until } = webdriver

module.exports = class WebDriverTodoList {
  constructor(baseUrl) {
    this._baseUrl = baseUrl
    this._driver = this.buildDriver(new webdriver.Builder()).build()
  }

  buildDriver(builder) {
    return builder.forBrowser('firefox')
  }

  async start() {
    await this._driver.get(this._baseUrl + '/')
    const todoListLocator = By.css('[aria-label="Todos"] ol')
    await this._driver.wait(until.elementLocated(todoListLocator), 10000)
  }

  async addTodo({ text }) {
    const textField = this._driver.findElement(By.css('[aria-label="New Todo Text"]'))
    await textField.sendKeys(text)
    const count = (await this.getTodos()).length
    await this._driver.findElement(By.css('[aria-label="Add Todo"]')).click()
    await this._driver.wait(async () => (await this.getTodos()).length > count, 10000)
  }

  async getTodos() {
    return this._driver.executeScript(() =>
      [...document.querySelectorAll('[aria-label="Todos"] li label')].map(label => ({
        text: label.innerText
      }))
    )
  }

  async stop() {
    await this._driver.quit()
  }
}
Download .txt
gitextract_shujpsec/

├── .circleci/
│   └── config.yml
├── .gitignore
├── LICENSE
├── Procfile
├── README.md
├── cucumber
├── cucumber-electron
├── cucumber.js
├── features/
│   ├── run/
│   │   ├── all
│   │   ├── all.cmd
│   │   ├── browserstack-memory
│   │   ├── browserstack-memory.cmd
│   │   ├── database
│   │   ├── database.cmd
│   │   ├── dom-http-memory
│   │   ├── dom-http-memory.cmd
│   │   ├── dom-memory
│   │   ├── dom-memory.cmd
│   │   ├── http-memory
│   │   ├── http-memory.cmd
│   │   ├── memory
│   │   ├── memory.cmd
│   │   ├── webdriver-http-database
│   │   ├── webdriver-http-database.cmd
│   │   ├── webdriver-memory
│   │   └── webdriver-memory.cmd
│   ├── step_definitions/
│   │   └── todo_steps.js
│   ├── support/
│   │   ├── assemblies/
│   │   │   ├── browserstack-memory.js
│   │   │   ├── database.js
│   │   │   ├── dom-http-memory.js
│   │   │   ├── dom-memory.js
│   │   │   ├── http-memory.js
│   │   │   ├── memory.js
│   │   │   ├── webdriver-http-database.js
│   │   │   └── webdriver-memory.js
│   │   ├── parameter_types.js
│   │   └── world.js
│   └── todo.feature
├── lib/
│   ├── client/
│   │   ├── BrowserApp.js
│   │   ├── HttpTodoList.js
│   │   └── client.js
│   └── server/
│       ├── DatabaseTodoList.js
│       ├── WebApp.js
│       ├── asyncExpress.js
│       └── start.js
├── package.json
├── public/
│   ├── index.html
│   └── todo.css
├── test/
│   └── MemoryTodoListTest.js
└── test_support/
    ├── BrowserStackTodoList.js
    ├── DomTodoList.js
    ├── MemoryTodoList.js
    └── WebDriverTodoList.js
Download .txt
SYMBOL INDEX (77 symbols across 18 files)

FILE: features/support/assemblies/browserstack-memory.js
  method start (line 6) | async start () {
  method stop (line 14) | async stop () {
  method contextTodoList (line 19) | async contextTodoList() {
  method actionTodoList (line 23) | async actionTodoList() {
  method outcomeTodoList (line 27) | async outcomeTodoList() {

FILE: features/support/assemblies/database.js
  method start (line 4) | async start () {
  method stop (line 9) | async stop() {
  method contextTodoList (line 13) | async contextTodoList() {
  method actionTodoList (line 17) | async actionTodoList() {
  method outcomeTodoList (line 21) | async outcomeTodoList() {

FILE: features/support/assemblies/dom-http-memory.js
  method start (line 10) | async start () {
  method stop (line 15) | async stop() {}
  method contextTodoList (line 17) | async contextTodoList() {
  method actionTodoList (line 21) | async actionTodoList() {
  method outcomeTodoList (line 25) | async outcomeTodoList() {
  method _makeDomTodoList (line 29) | async _makeDomTodoList() {
  method _makeHttpTodoList (line 39) | async _makeHttpTodoList() {

FILE: features/support/assemblies/dom-memory.js
  method start (line 8) | async start () {
  method stop (line 12) | async stop() {}
  method contextTodoList (line 14) | async contextTodoList() {
  method actionTodoList (line 18) | async actionTodoList() {
  method outcomeTodoList (line 22) | async outcomeTodoList() {
  method _makeDomTodoList (line 26) | async _makeDomTodoList() {

FILE: features/support/assemblies/http-memory.js
  method start (line 6) | async start () {
  method stop (line 13) | async stop () {
  method contextTodoList (line 17) | async contextTodoList() {
  method actionTodoList (line 21) | async actionTodoList() {
  method outcomeTodoList (line 25) | async outcomeTodoList() {

FILE: features/support/assemblies/memory.js
  method start (line 4) | async start () {
  method stop (line 8) | async stop() {}
  method contextTodoList (line 10) | async contextTodoList() {
  method actionTodoList (line 14) | async actionTodoList() {
  method outcomeTodoList (line 18) | async outcomeTodoList() {

FILE: features/support/assemblies/webdriver-http-database.js
  method start (line 6) | async start () {
  method stop (line 14) | async stop () {
  method contextTodoList (line 19) | async contextTodoList() {
  method actionTodoList (line 23) | async actionTodoList() {
  method outcomeTodoList (line 28) | async outcomeTodoList() {

FILE: features/support/assemblies/webdriver-memory.js
  method start (line 6) | async start () {
  method stop (line 13) | async stop () {
  method contextTodoList (line 18) | async contextTodoList() {
  method actionTodoList (line 22) | async actionTodoList() {
  method outcomeTodoList (line 27) | async outcomeTodoList() {

FILE: features/support/parameter_types.js
  method transformer (line 6) | transformer(s) {

FILE: features/support/world.js
  class TodoWorld (line 9) | class TodoWorld {
    method constructor (line 10) | constructor() {

FILE: lib/client/BrowserApp.js
  method constructor (line 2) | constructor({ domNode, todoList }) {
  method mount (line 21) | async mount() {
  method addTodo (line 25) | async addTodo({ text }) {
  method renderTodos (line 38) | renderTodos(todos) {

FILE: lib/client/HttpTodoList.js
  method constructor (line 2) | constructor(baseUrl, fetch) {
  method addTodo (line 7) | async addTodo({ text }) {
  method getTodos (line 19) | async getTodos() {

FILE: lib/server/DatabaseTodoList.js
  method constructor (line 5) | constructor() {
  method start (line 15) | async start(truncate) {
  method stop (line 21) | async stop(truncate) {
  method addTodo (line 25) | async addTodo({ text }) {
  method getTodos (line 29) | async getTodos() {

FILE: lib/server/WebApp.js
  method constructor (line 7) | constructor({ todoList, serveClient }) {
  method buildApp (line 12) | async buildApp() {
  method listen (line 40) | async listen(port) {
  method stop (line 51) | async stop() {

FILE: test_support/BrowserStackTodoList.js
  method buildDriver (line 5) | buildDriver(builder) {
  method start (line 16) | async start() {
  method stop (line 26) | async stop() {

FILE: test_support/DomTodoList.js
  method constructor (line 2) | constructor(domNode) {
  method addTodo (line 6) | async addTodo({ text }) {
  method getTodos (line 17) | async getTodos() {

FILE: test_support/MemoryTodoList.js
  method constructor (line 2) | constructor() {
  method addTodo (line 6) | async addTodo({ text }) {
  method getTodos (line 10) | async getTodos() {

FILE: test_support/WebDriverTodoList.js
  method constructor (line 5) | constructor(baseUrl) {
  method buildDriver (line 10) | buildDriver(builder) {
  method start (line 14) | async start() {
  method addTodo (line 20) | async addTodo({ text }) {
  method getTodos (line 28) | async getTodos() {
  method stop (line 36) | async stop() {
Condensed preview — 53 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (36K chars).
[
  {
    "path": ".circleci/config.yml",
    "chars": 705,
    "preview": "version: 2\njobs:\n  build:\n    docker:\n      - image: circleci/node:8-browsers\n        environment:\n          DATABASE_UR"
  },
  {
    "path": ".gitignore",
    "chars": 72,
    "preview": "node_modules\nlocal.log\nbrowserstack.err\n.DS_Store\ntodo-subsecond.sqlite\n"
  },
  {
    "path": "LICENSE",
    "chars": 1088,
    "preview": "The MIT License (MIT)\n\nCopyright (c) Sub-Second TDD Organization\n\nPermission is hereby granted, free of charge, to any p"
  },
  {
    "path": "Procfile",
    "chars": 30,
    "preview": "web: node lib/server/start.js\n"
  },
  {
    "path": "README.md",
    "chars": 6771,
    "preview": "# TODO Subsecond\n\n[![CircleCI](https://circleci.com/gh/subsecondtdd/todo-subsecond.svg?style=svg)](https://circleci.com/"
  },
  {
    "path": "cucumber",
    "chars": 60,
    "preview": "#!/usr/bin/env bash\nnode node_modules/.bin/cucumber-js \"$@\"\n"
  },
  {
    "path": "cucumber-electron",
    "chars": 66,
    "preview": "#!/usr/bin/env bash\nnode node_modules/.bin/cucumber-electron \"$@\"\n"
  },
  {
    "path": "cucumber.js",
    "chars": 87,
    "preview": "module.exports = {\n  default: '--format-options \\'{\"snippetInterface\": \"promise\"}\\''\n}\n"
  },
  {
    "path": "features/run/all",
    "chars": 275,
    "preview": "#!/usr/bin/env bash\nfeatures/run/memory && \\\nfeatures/run/dom-memory && \\\nfeatures/run/http-memory && \\\nfeatures/run/dom"
  },
  {
    "path": "features/run/all.cmd",
    "chars": 614,
    "preview": "@echo off\ncall features\\run\\memory\nif %errorlevel% neq 0 exit /b %errorlevel%\ncall features\\run\\dom-memory\nif %errorleve"
  },
  {
    "path": "features/run/browserstack-memory",
    "chars": 233,
    "preview": "#!/usr/bin/env bash\nif [[ -z \"${BROWSERSTACK_USER}\" ]]; then\n  echo \"set BROWSERSTACK_USER and BROWSERSTACK_KEY to run f"
  },
  {
    "path": "features/run/browserstack-memory.cmd",
    "chars": 213,
    "preview": "@echo off\nIF \"%BROWSERSTACK_USER%\"==\"\" (\n  echo set BROWSERSTACK_USER and BROWSERSTACK_KEY to run features on browsersta"
  },
  {
    "path": "features/run/database",
    "chars": 87,
    "preview": "#!/usr/bin/env bash\nCUCUMBER_ASSEMBLY=database node node_modules/.bin/cucumber-js \"$@\"\n"
  },
  {
    "path": "features/run/database.cmd",
    "chars": 74,
    "preview": "@echo off\nset CUCUMBER_ASSEMBLY=database\nnode_modules\\.bin\\cucumber-js %*\n"
  },
  {
    "path": "features/run/dom-http-memory",
    "chars": 100,
    "preview": "#!/usr/bin/env bash\nCUCUMBER_ASSEMBLY=dom-http-memory node node_modules/.bin/cucumber-electron \"$@\"\n"
  },
  {
    "path": "features/run/dom-http-memory.cmd",
    "chars": 87,
    "preview": "@echo off\nset CUCUMBER_ASSEMBLY=dom-http-memory\nnode_modules\\.bin\\cucumber-electron %*\n"
  },
  {
    "path": "features/run/dom-memory",
    "chars": 95,
    "preview": "#!/usr/bin/env bash\nCUCUMBER_ASSEMBLY=dom-memory node node_modules/.bin/cucumber-electron \"$@\"\n"
  },
  {
    "path": "features/run/dom-memory.cmd",
    "chars": 82,
    "preview": "@echo off\nset CUCUMBER_ASSEMBLY=dom-memory\nnode_modules\\.bin\\cucumber-electron %*\n"
  },
  {
    "path": "features/run/http-memory",
    "chars": 90,
    "preview": "#!/usr/bin/env bash\nCUCUMBER_ASSEMBLY=http-memory node node_modules/.bin/cucumber-js \"$@\"\n"
  },
  {
    "path": "features/run/http-memory.cmd",
    "chars": 77,
    "preview": "@echo off\nset CUCUMBER_ASSEMBLY=http-memory\nnode_modules\\.bin\\cucumber-js %*\n"
  },
  {
    "path": "features/run/memory",
    "chars": 85,
    "preview": "#!/usr/bin/env bash\nCUCUMBER_ASSEMBLY=memory node node_modules/.bin/cucumber-js \"$@\"\n"
  },
  {
    "path": "features/run/memory.cmd",
    "chars": 72,
    "preview": "@echo off\nset CUCUMBER_ASSEMBLY=memory\nnode_modules\\.bin\\cucumber-js %*\n"
  },
  {
    "path": "features/run/webdriver-http-database",
    "chars": 102,
    "preview": "#!/usr/bin/env bash\nCUCUMBER_ASSEMBLY=webdriver-http-database node node_modules/.bin/cucumber-js \"$@\"\n"
  },
  {
    "path": "features/run/webdriver-http-database.cmd",
    "chars": 89,
    "preview": "@echo off\nset CUCUMBER_ASSEMBLY=webdriver-http-database\nnode_modules\\.bin\\cucumber-js %*\n"
  },
  {
    "path": "features/run/webdriver-memory",
    "chars": 95,
    "preview": "#!/usr/bin/env bash\nCUCUMBER_ASSEMBLY=webdriver-memory node node_modules/.bin/cucumber-js \"$@\"\n"
  },
  {
    "path": "features/run/webdriver-memory.cmd",
    "chars": 82,
    "preview": "@echo off\nset CUCUMBER_ASSEMBLY=webdriver-memory\nnode_modules\\.bin\\cucumber-js %*\n"
  },
  {
    "path": "features/step_definitions/todo_steps.js",
    "chars": 686,
    "preview": "const assert = require('assert')\nconst { Given, When, Then, setDefaultTimeout } = require('cucumber')\n\nsetDefaultTimeout"
  },
  {
    "path": "features/support/assemblies/browserstack-memory.js",
    "chars": 898,
    "preview": "const BrowserStackTodoList = require('../../../test_support/BrowserStackTodoList')\nconst MemoryTodoList = require('../.."
  },
  {
    "path": "features/support/assemblies/database.js",
    "chars": 500,
    "preview": "const DatabaseTodoList = require('../../../lib/server/DatabaseTodoList')\n\nmodule.exports = class DatabaseAssembly {\n  as"
  },
  {
    "path": "features/support/assemblies/dom-http-memory.js",
    "chars": 1376,
    "preview": "const fs = require('fs')\nconst path = require('path')\nconst DomTodoList = require('../../../test_support/DomTodoList')\nc"
  },
  {
    "path": "features/support/assemblies/dom-memory.js",
    "chars": 1023,
    "preview": "const fs = require('fs')\nconst path = require('path')\nconst DomTodoList = require('../../../test_support/DomTodoList')\nc"
  },
  {
    "path": "features/support/assemblies/http-memory.js",
    "chars": 752,
    "preview": "const MemoryTodoList = require('../../../test_support/MemoryTodoList')\nconst WebApp = require('../../../lib/server/WebAp"
  },
  {
    "path": "features/support/assemblies/memory.js",
    "chars": 398,
    "preview": "const MemoryTodoList = require('../../../test_support/MemoryTodoList')\n\nmodule.exports = class MemoryAssembly {\n  async "
  },
  {
    "path": "features/support/assemblies/webdriver-http-database.js",
    "chars": 928,
    "preview": "const WebDriverTodoList = require('../../../test_support/WebDriverTodoList')\nconst DatabaseTodoList = require('../../../"
  },
  {
    "path": "features/support/assemblies/webdriver-memory.js",
    "chars": 871,
    "preview": "const WebDriverTodoList = require('../../../test_support/WebDriverTodoList')\nconst MemoryTodoList = require('../../../te"
  },
  {
    "path": "features/support/parameter_types.js",
    "chars": 180,
    "preview": "const { defineParameterType } = require('cucumber')\n\ndefineParameterType({\n  name: 'ordinal',\n  regexp: /(\\d+)(?:st|nd|r"
  },
  {
    "path": "features/support/world.js",
    "chars": 627,
    "preview": "const { setWorldConstructor, Before, After } = require('cucumber')\n\nconst assemblyName = process.env.CUCUMBER_ASSEMBLY |"
  },
  {
    "path": "features/todo.feature",
    "chars": 362,
    "preview": "Feature: Todo\n\n  Scenario: Create Todo\n    Given there is already 1 todo\n    When I add \"get milk\"\n    Then the text of "
  },
  {
    "path": "lib/client/BrowserApp.js",
    "chars": 1725,
    "preview": "module.exports = class BrowserApp {\n  constructor({ domNode, todoList }) {\n    this._domNode = domNode\n    this._todoLis"
  },
  {
    "path": "lib/client/HttpTodoList.js",
    "chars": 639,
    "preview": "module.exports = class HttpTodoList {\n  constructor(baseUrl, fetch) {\n    this._baseUrl = baseUrl\n    this._fetch = fetc"
  },
  {
    "path": "lib/client/client.js",
    "chars": 205,
    "preview": "const BrowserApp = require('./BrowserApp')\nconst HttpTodoList = require('./HttpTodoList')\n\nnew BrowserApp({\n  domNode: d"
  },
  {
    "path": "lib/server/DatabaseTodoList.js",
    "chars": 857,
    "preview": "const Sequelize = require('sequelize');\n\nmodule.exports = class DatabaseTodoList {\n\n  constructor() {\n    const database"
  },
  {
    "path": "lib/server/WebApp.js",
    "chars": 1491,
    "preview": "const http = require('http')\nconst express = require('express')\nconst bodyParser = require('body-parser')\nconst asyncExp"
  },
  {
    "path": "lib/server/asyncExpress.js",
    "chars": 349,
    "preview": "const wrap = asyncFn => (req, res, next) => {\n  asyncFn(req, res, next).catch(err => next(err))\n}\n\nmodule.exports = func"
  },
  {
    "path": "lib/server/start.js",
    "chars": 315,
    "preview": "const WebApp = require('./WebApp')\nconst DatabaseTodoList = require('./DatabaseTodoList')\n\nconst port = process.env.PORT"
  },
  {
    "path": "package.json",
    "chars": 902,
    "preview": "{\n  \"name\": \"todo-subsecond\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"contributors\": [\n    \"Josh Chisholm <joshua"
  },
  {
    "path": "public/index.html",
    "chars": 412,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>todos</title>\n    <link rel=\"stylesheet\" href=\"/to"
  },
  {
    "path": "public/todo.css",
    "chars": 1356,
    "preview": "* {\n  box-sizing: border-box;\n}\nbody {\n  font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;\n  background-color: "
  },
  {
    "path": "test/MemoryTodoListTest.js",
    "chars": 375,
    "preview": "const assert = require('assert')\nconst MemoryTodoList = require('../test_support/MemoryTodoList')\n\ndescribe('MemoryTodoL"
  },
  {
    "path": "test_support/BrowserStackTodoList.js",
    "chars": 946,
    "preview": "const WebDriverTodoList = require('./WebDriverTodoList')\nconst browserstack = require('browserstack-local')\n\nmodule.expo"
  },
  {
    "path": "test_support/DomTodoList.js",
    "chars": 571,
    "preview": "module.exports = class DomTodoList {\n  constructor(domNode) {\n    this._domNode = domNode\n  }\n\n  async addTodo({ text })"
  },
  {
    "path": "test_support/MemoryTodoList.js",
    "chars": 198,
    "preview": "module.exports = class MemoryTodoList {\n  constructor() {\n    this._todos = []\n  }\n\n  async addTodo({ text }) {\n    this"
  },
  {
    "path": "test_support/WebDriverTodoList.js",
    "chars": 1153,
    "preview": "const webdriver = require('selenium-webdriver')\nconst { By, until } = webdriver\n\nmodule.exports = class WebDriverTodoLis"
  }
]

About this extraction

This page contains the full source code of the subsecondtdd/todo-subsecond GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 53 files (30.8 KB), approximately 9.3k tokens, and a symbol index with 77 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!