Article source: github.com/Haixiang612…

Reference the wheels: www.npmjs.com/package/sup…

Supertest is a short and elegant interface test tool, such as a login interface test for example:

import request from 'supertest'

it('Login successful'.() = > {
  request('https://127.0.0.1:8080')
    .post('/login')
    .send({ username: 'HaiGuai'.password: '123456' })
    .expect(200)})Copy the code

The whole use case is very simple and understandable in feel. The library is small, well designed and written by TJ Holowaychuk! Today we will take you to realize a supertest wheel, do a test framework!

Train of thought

Before writing the code, design the entire framework based on the classic example above.

As can be seen from the above example, sending the request, processing the request, and expecting the result constitute the link of the whole framework and the life cycle of a use case.

request -> process -> expect(200)
Copy the code

The request step can be implemented by third-party HTTP libraries such as AXIos, Node-FETCH, and SuperAgent.

The process step is left out of business code, and finally Expect can use node.js’s assert library to execute assertions. So let’s focus on how to enforce these assertions.

The last expect step is the whole heart of our framework, and it’s all about managing all assertions, because developers are likely to execute assertions as many times as the following:

xxx
  .expect(1 + 1, 2)
  .expect(200)
  .expect({ result: 'success'})
  .expect((res) => console.log(res))
Copy the code

Therefore, we need an array this._implies = [] to hold these assertions, and then provide an end() function for the last one-time execution of these assertions:

Expect ({result: 'success'}). Expect ((res) => console.log(res)).end(Copy the code

It’s a bit like the event center, except that each expect is like adding a listener to the “expect” event, and the end is like triggering the “Expect” event, executing all the listeners.

We also note that some Expect functions may be used to check the status code, some for the returned body and some for the headers, so every time we call the Expect function, in addition to pushing the assertion callback to this. _implies, Also determine whether the assertion callback pushed is for a HEADERS, body, or status assertion.

The above ideas are sorted out as follows:

We only need to focus on the yellow and red parts.

Simple implementation

The “send request” step can be done by the third party library, here we choose superagent as the send NPM package, because the use of this library is also the chain call is more suitable for our expectations, for example:

superagent
  .post('/api/pet')
  .send({ name: 'Manny'.species: 'cat' }) // sends a JSON post body
  .set('X-API-Key'.'foobar')
  .set('accept'.'json')
  .end((err, res) = > {
    // Calling the end function will send the request
  });
Copy the code

This is too similar! This gives us some inspiration: based on superagent, add the above expect to superagent, and then rewrite the end and restful HTTP functions. “Based on XX, rewrite methods and add your own methods”, what comes to mind? Inheritance! Superagent provides the Request class, so we just need to inherit it, rewrite methods and add Expect functions!

A simple Request subclass implements the following (regardless of the assertion callback distinction, just do a simple Equals callback) :

import {Request} from 'superagent'
import assert from 'assert'

function Test(url, method, path) {
  // Send the request
  Request.call(this, method.toUpperCase(), path)

  this.url = url + path // Request path
  this._asserts = [] / / an Assertion queue
}

/ / Request
Object.setPrototypeOf(Test.prototype, Request.prototype)

/** * .expect(1 + 1, 2) */
Test.prototype.expect = function(a, b) {
  this._asserts.push(this.equals.bind(this, a, b))

  return this
}

// Check whether the two values are equal
Test.prototype.equals = function(a, b) {
  try {
    assert.strictEqual(a, b)
  } catch (err) {
    return new Error(` I want${a}But you gave it to me${b}`)}}// Execute all assertions
Test.prototype.assert = function(err, res, fn) {
  let errorObj = null

  for (let i = 0; i < this._asserts.length; i++) {
    errorObj = this._asserts[i](res)
  }

  fn(errorObj)
}

// Aggregate all Assertion results
Test.prototype.end = function (fn) {
  const self = this
  const end = Request.prototype.end

  end.call(this.function(err, res) {
    self.assert(err, res, fn)
  })

  return this
}
Copy the code

The above inherits the Request parent, provides expect, equals, and assert functions, and overwrites the end function. This is just our own Test class. It is best to provide a Request function externally:

import methods from 'methods'
import http from 'http'
import Test from './Test'

function request(path) {
  const obj = {}

  methods.forEach(function(method) {
    obj[method] = function(url) {
      return new Test(path, method, url)
    }
  })

  obj.del = obj.delete

  return obj
}
Copy the code

Methods the NPM package returns all restful function names, such as POST, GET, and so on. Add these restful functions to the newly created object, create the Test object by passing in the corresponding path, method, and URL, and then indirectly create an HTTP request to complete the “send request” step.

Then we can use our framework like this:

it('should be supported'.function (done) {
  const app = express();
  let s;

  app.get('/'.function (req, res) {
    res.send('hello');
  });

  s = app.listen(function () {
    const url = 'http://localhost:' + s.address().port;
    request(url)
      .get('/')
      .expect(1 + 1.1)
      .end(done);
  });
});
Copy the code

Creating a server

Listen (app.listen, app.listen, app.listen, app.listen, app.listen) Such as:

it('should fire up the app on an ephemeral port'.function (done) {
  const app = express();

  app.get('/'.function (req, res) {
    res.send('hey');
  });

  request(app)
    .get('/')
    .end(function (err, res) {
      expect(res.status).toEqual(200)
      expect(res.text).toEqual('hey')
      done();
    });
});
Copy the code

First, we check in the request function to create a server if the app function is passed in.

function request(app) {
  const obj = {}

  if (typeof app === 'function') {
    app = http.createServer(app) // Create an internal server
  }

  methods.forEach(function(method) {
    obj[method] = function(url) {
      return new Test(app, method, url)
    }
  })

  obj.del = obj.delete

  return obj
}
Copy the code

The constructor of the Test class can also get the corresponding path and listen for port 0:

function Test(app, method, path) {
  // Send the request
  Request.call(this, method.toUpperCase(), path)

  this.redirects(0) // Disable redirection
  this.app = app // app/string
  this.url = typeof app === 'string' ? app + path : this.serverAddress(app, path) // Request path
  this._asserts = [] / / an Assertion queue
}

// Get the request path through app
Test.prototype.serverAddress = function(app, path) {
  if(! app.address()) {this._server = app.listen(0) / / internal server
  }

  const port = app.address().port
  const protocol = app instanceof https.Server ? 'https' : 'http'
  return `${protocol}: / / 127.0.0.1:${port}${path}`
}
Copy the code

Finally, close the newly created server in the end function:

// Aggregate all Assertion results
Test.prototype.end = function (fn) {
  const self = this
  const server = this._server
  const end = Request.prototype.end

  end.call(this.function(err, res) {
    if (server && server._handle) return server.close(localAssert)

    localAssert()

    function localAssert() {
      self.assert(err, res, fn)
    }
  })

  return this
}
Copy the code

Encapsulate error information

Again, let’s see how we handle assertions: failed assertions go to the catch statement and return an Error, which is passed to the FN callback parameter of end(fn). There is a problem, however, when we look at the error stack:

The error messages are as expected, but the error stack is not so friendly: the first three lines locate in our own frame code! Imagine if someone made an error with our library Expect, clicked the error stack result, and found out that they had located our source code? Therefore, we need to modify the Error err.stack:

// Wrap the original function to provide a more elegant error stack
function wrapAssertFn(assertFn) {
  // Keep the last 3 lines
  const savedStack = new Error().stack.split('\n').slice(3)

  return function(res) {
    const err = assertFn(res)
    if (err instanceof Error && err.stack) {
      // Get rid of line 1
      const badStack = err.stack.replace(err.message, ' ').split('\n').slice(1)
      err.stack = [err.toString()]
        .concat(savedStack)
        .concat('-- -- -- -- -- -- -- --)
        .concat(badStack)
        .join('\n')}return err
  }
}

Test.prototype.expect = function(a, b) {
  this._asserts.push(wrapAssertFn(this.equals.bind(this, a, b)))

  return this
}
Copy the code

Above first remove the first 3 lines of the current call stack, that is, the first 3 lines of the above screenshot, because this is the source code error, there will be interference to the developer, and the stack can help developers directly locate the cold expect. Of course, we also display the actual source code error as badStack, but with ‘——‘ as the partition, the final error result is as follows:

Distinguish between assertion callbacks

Now that we have just implemented the simplest assertions using equal, let’s add headers, status, and body assertions. A simple implementation of their assertion functions looks like this:

import util from "util";
import assert from 'assert'

// Determine whether the current status code is equal
Test.prototype._assertStatus = function(status, res) {
  if(status ! == res.status) {const expectStatusContent = http.STATUS_CODES[status]
    const actualStatusContent = http.STATUS_CODES[res.status]
    return new Error('expected ' + status + '"' + expectStatusContent + '", got ' + res.status + '"' + actualStatusContent + '"')}}// Determine whether the current body is equal
// Determine whether the current body is equal
Test.prototype._assertBody = function(body, res) {
  const isRegExp = body instanceof RegExp

  if (typeof body === 'object' && !isRegExp) { // Compare the normal body
    try {
      assert.deepStrictEqual(body, res.body)
    } catch (err) {
      const expectBody = util.inspect(body)
      const actualBody = util.inspect(res.body)
      return error('expected ' + expectBody + ' response body, got '+ actualBody, body, res.body); }}else if(body ! == res.text) {// Compare normal text content
    const expectBody = util.inspect(body)
    const actualBody = util.inspect(res.text)

    if (isRegExp) {
      if(! body.test(res.text)) {// Body is a regular expression case
        return error('expected body ' + actualBody + ' to match '+ body, body, res.body); }}else {
      return error(`expected ${expectBody} response body, got ${actualBody}`, body, res.body)
    }
  }
}

// Check whether the current header is equal
Test.prototype._assertHeader = function(header, res) {
  const field = header.name
  const actualValue = res.header[field.toLowerCase()]
  const expectValue = header.value

  // Field does not exist
  if (typeof actualValue === 'undefined') {
    return new Error('expected "' + field + '" header field');
  }
  // It is the same case
  if ((Array.isArray(actualValue) && actualValue.toString() === expectValue) || actualValue === expectValue) {
    return
  }
  // Check the re case
  if (expectValue instanceof RegExp) {
    if(! expectValue.test(actualValue)) {return new Error('expected "' + field + '" matching ' + expectValue + ', got "' + actualValue + '"')}}else {
    return new Error('expected "' + field + '" of "' + expectValue + '", got "' + actualValue + '"')}}// Optimize the error display
function error(msg, expected, actual) {
  const err = new Error(msg)
  err.expected = expected
  err.actual = actual
  err.showDiff = true
  return err
}
Copy the code

Then select the corresponding _assertXXX function from the expect function by determining the parameter type:

/** * .expect(200) * .expect(200, fn) * .expect(200, body) * .expect('Some body') * .expect('Some body', fn) * .expect('Content-Type', 'application/json') * .expect('Content-Type', 'application/json', fn) * .expect(fn) */
Test.prototype.expect = function(a, b, c) {
  / / callback
  if (typeof a === 'function') {
    this._asserts.push(wrapAssertFn(a))
    return this
  }
  if (typeof b === 'function') this.end(b)
  if (typeof c === 'function') this.end(c)

  / / status code
  if (typeof a === 'number') {
    this._asserts.push(wrapAssertFn(this._assertStatus.bind(this, a)))
    // body
    if (typeofb ! = ='function' && arguments.length > 1) {
      this._asserts.push(wrapAssertFn(this._assertBody.bind(this, b)))
    }
    return this
  }

  // header
  if (typeof b === 'string' || typeof b === 'number' || b instanceof RegExp) {
    this._asserts.push(wrapAssertFn(this._assertHeader.bind(this, { name: ' ' + a, value: b })))
    return this
  }

  // body
  this._asserts.push(wrapAssertFn(this._assertBody.bind(this, a)))

  return this
}
Copy the code

At this point, we have completed the basic assertion functionality.

Handling network errors

Sometimes errors are thrown not because of business code errors, but because of exceptions such as network outages. We also need to handle these errors and show them to developers in a more user-friendly way, by modifying the Assert function:

// Execute all assertions
Test.prototype.assert = function(resError, res, fn) {
  // Common network error
  const sysErrors = {
    ECONNREFUSED: 'Connection refused'.ECONNRESET: 'Connection reset by peer'.EPIPE: 'Broken pipe'.ETIMEDOUT: 'Operation timed out'
  };

  let errorObj = null

  // Handle the returned error
  if(! res && resError) {if (resError instanceof Error && resError.syscall === 'connect' && sysErrors[resError.code]) {
      errorObj = new Error(resError.code + ':' + sysErrors[resError.code])
    } else {
      errorObj = resError
    }
  }

  // Execute all assertions
  for (let i = 0; i < this._asserts.length && ! errorObj; i++) { errorObj =this._assertFunction(this._asserts[i], res)
  }

  // Handle superagent errors
  if(! errorObj && resErrorinstanceof Error&& (! res || resError.status ! == res.status)) { errorObj = resError } fn.call(this, errorObj || null, res)
}
Copy the code

So far, status, body, and headers assertions have been implemented, and expect uses all three assertion callbacks reasonably, while handling network exceptions.

The Agent Agent

Let’s review how we used frameworks to write test cases:

it('should handle redirects'.function (done) {
  const app = express();

  app.get('/login'.function (req, res) {
    res.end('Login');
  });

  app.get('/'.function (req, res) {
    res.redirect('/login');
  });

  request(app)
    .get('/')
    .redirects(1)
    .end(function (err, res) {
      expect(res).toBeTruthy()
      expect(res.status).toEqual(200)
      expect(res.text).toEqual('Login')
      done();
    });
});
Copy the code

As you can see, each call to the request function creates a server immediately inside, and then shuts it down immediately after the call to end. Continuous testing is very expensive and can easily share a server. Can we use A_Server for series A cases and B_Server for series B cases?

In addition to the Request class, SuperAgent also provides a powerful Agent class to address these requirements. TestAgent class TestAgent class TestAgent class TestAgent

import http from 'http'
import methods from 'methods'
import {agent as Agent} from 'superagent'

import Test from './Test'

function TestAgent(app, options) {
  // Call TestAgent(app, options)
  if(! (this instanceof TestAgent)) {
    return new TestAgent(app, options)
  }

  // Create a server
  if (typeof app === 'function') {
    app = http.createServer(app)
  }

  // https
  if (options) {
    this._ca = options.ca
    this._key = options.key
    this._cert = options.cert
  }

  // The agent that uses superagent
  Agent.call(this)
  this.app = app
}

/ / Agent
Object.setPrototypeOf(TestAgent.prototype, Agent.prototype)

/ / host function
TestAgent.prototype.host = function(host) {
  this._host = host
  return this
}

// delete
TestAgent.prototype.del = TestAgent.prototype.delete
Copy the code

Don’t forget to overload restful methods as well:

// Override HTTP restful method
methods.forEach(function(method) {
  TestAgent.prototype[method] = function(url, fn) {
    // Initialize the request
    const req = new Test(this.app, method.toLowerCase(), url)

    // https
    req.ca(this._ca)
    req.key(this._key)
    req.cert(this._cert)

    // host
    if (this._host) {
      req.set('host'.this._host)
    }

    // Save cookies when HTTP returns
    req.on('response'.this._saveCookies.bind(this))
    // The redirection saves the Cookie along with the Cookie
    req.on('redirect'.this._saveCookies.bind(this))
    req.on('redirect'.this._attachCookies.bind(this))

    // This request takes a Cookie
    this._attachCookies(req)
    this._setDefaults(req)

    return req
  }
})
Copy the code

In addition to returning the created Test object, HTTPS, host, and cookie are also handled. In fact, these processing is not my idea, is the superagent in its own Agent class processing, here just copy over 🙂

Using Class Inheritance

All of the above uses prototype to implement inheritance, very painful. TestAgent = TestAgent; TestAgent = TestAgent;

// Test.js
import http from 'http'
import https from 'https'
import assert from 'assert'
import {Request} from 'superagent'
import util from 'util'

// Wrap the original function to provide a more elegant error stack
function wrapAssertFn(assertFn) {
  // Keep the last 3 lines
  const savedStack = new Error().stack.split('\n').slice(3)

  return function (res) {
    const err = assertFn(res)
    if (err instanceof Error && err.stack) {
      // Get rid of line 1
      const badStack = err.stack.replace(err.message, ' ').split('\n').slice(1)
      err.stack = [err.toString()]
        .concat(savedStack)
        .concat('-- -- -- -- -- -- -- --)
        .concat(badStack)
        .join('\n')}return err
  }
}

// Optimize the error display
function error(msg, expected, actual) {
  const err = new Error(msg)
  err.expected = expected
  err.actual = actual
  err.showDiff = true
  return err
}

class Test extends Request {
  / / initialization
  constructor(app, method, path) {
    super(method.toUpperCase(), path)

    this.redirects(0) // Disable redirection
    this.app = app // app/string
    this.url = typeof app === 'string' ? app + path : this.serverAddress(app, path) // Request path
    this._asserts = [] / / an Assertion queue
  }

  // Get the request path through app
  serverAddress(app, path) {
    if(! app.address()) {this._server = app.listen(0) / / internal server
    }

    const port = app.address().port
    const protocol = app instanceof https.Server ? 'https' : 'http'
    return `${protocol}: / / 127.0.0.1:${port}${path}`
  }

  /** * .expect(200) * .expect(200, fn) * .expect(200, body) * .expect('Some body') * .expect('Some body', fn) * .expect('Content-Type', 'application/json') * .expect('Content-Type', 'application/json', fn) * .expect(fn) */
  expect(a, b, c) {
    / / callback
    if (typeof a === 'function') {
      this._asserts.push(wrapAssertFn(a))
      return this
    }
    if (typeof b === 'function') this.end(b)
    if (typeof c === 'function') this.end(c)

    / / status code
    if (typeof a === 'number') {
      this._asserts.push(wrapAssertFn(this._assertStatus.bind(this, a)))
      // body
      if (typeofb ! = ='function' && arguments.length > 1) {
        this._asserts.push(wrapAssertFn(this._assertBody.bind(this, b)))
      }
      return this
    }

    // header
    if (typeof b === 'string' || typeof b === 'number' || b instanceof RegExp) {
      this._asserts.push(wrapAssertFn(this._assertHeader.bind(this, {name: ' ' + a, value: b})))
      return this
    }

    // body
    this._asserts.push(wrapAssertFn(this._assertBody.bind(this, a)))

    return this
  }

  // Aggregate all Assertion results
  end(fn) {
    const self = this
    const server = this._server
    const end = Request.prototype.end

    end.call(this.function (err, res) {
      if (server && server._handle) return server.close(localAssert)

      localAssert()

      function localAssert() {
        self.assert(err, res, fn)
      }
    })

    return this
  }

  // Execute all assertions
  assert(resError, res, fn) {
    // Common network error
    const sysErrors = {
      ECONNREFUSED: 'Connection refused'.ECONNRESET: 'Connection reset by peer'.EPIPE: 'Broken pipe'.ETIMEDOUT: 'Operation timed out'
    }

    let errorObj = null

    // Handle the returned error
    if(! res && resError) {if (resError instanceof Error && resError.syscall === 'connect' && sysErrors[resError.code]) {
        errorObj = new Error(resError.code + ':' + sysErrors[resError.code])
      } else {
        errorObj = resError
      }
    }

    // Execute all assertions
    for (let i = 0; i < this._asserts.length && ! errorObj; i++) { errorObj =this._assertFunction(this._asserts[i], res)
    }

    // Handle superagent errors
    if(! errorObj && resErrorinstanceof Error&& (! res || resError.status ! == res.status)) { errorObj = resError } fn.call(this, errorObj || null, res)
  }

  // Determine whether the current status code is equal
  _assertStatus(status, res) {
    if(status ! == res.status) {const expectStatusContent = http.STATUS_CODES[status]
      const actualStatusContent = http.STATUS_CODES[res.status]
      return new Error('expected ' + status + '"' + expectStatusContent + '", got ' + res.status + '"' + actualStatusContent + '"')}}// Determine whether the current body is equal
  _assertBody(body, res) {
    const isRegExp = body instanceof RegExp

    if (typeof body === 'object' && !isRegExp) { // Compare the normal body
      try {
        assert.deepStrictEqual(body, res.body)
      } catch (err) {
        const expectBody = util.inspect(body)
        const actualBody = util.inspect(res.body)
        return error('expected ' + expectBody + ' response body, got ' + actualBody, body, res.body)
      }
    } else if(body ! == res.text) {// Compare normal text content
      const expectBody = util.inspect(body)
      const actualBody = util.inspect(res.text)

      if (isRegExp) {
        if(! body.test(res.text)) {// Body is a regular expression case
          return error('expected body ' + actualBody + ' to match ' + body, body, res.body)
        }
      } else {
        return error(`expected ${expectBody} response body, got ${actualBody}`, body, res.body)
      }
    }
  }

  // Check whether the current header is equal
  _assertHeader(header, res) {
    const field = header.name
    const actualValue = res.header[field.toLowerCase()]
    const expectValue = header.value

    // Field does not exist
    if (typeof actualValue === 'undefined') {
      return new Error('expected "' + field + '" header field')}// It is the same case
    if ((Array.isArray(actualValue) && actualValue.toString() === expectValue) || actualValue === expectValue) {
      return
    }
    // Check the re case
    if (expectValue instanceof RegExp) {
      if(! expectValue.test(actualValue)) {return new Error('expected "' + field + '" matching ' + expectValue + ', got "' + actualValue + '"')}}else {
      return new Error('expected "' + field + '" of "' + expectValue + '", got "' + actualValue + '"')}}// Execute a single Assertion
  _assertFunction(fn, res) {
    let err
    try {
      err = fn(res)
    } catch (e) {
      err = e
    }
    if (err instanceof Error) return err
  }
}

export default Test
Copy the code

There are TestAgent

import http from 'http'
import methods from 'methods'
import {agent as Agent} from 'superagent'

import Test from './Test'

class TestAgent extends Agent {
  / / initialization
  constructor(app, options) {
    super(a)// Create a server
    if (typeof app === 'function') {
      app = http.createServer(app)
    }

    // https
    if (options) {
      this._ca = options.ca
      this._key = options.key
      this._cert = options.cert
    }

    // The agent that uses superagent
    Agent.call(this)
    this.app = app
  }

  / / host function
  host(host) {
    this._host = host
    return this
  }

  / / reuse delete
  del(. args) {
    this.delete(args)
  }
}

// Override HTTP restful method
methods.forEach(function (method) {
  TestAgent.prototype[method] = function (url, fn) {
    // Initialize the request
    const req = new Test(this.app, method.toLowerCase(), url)

    // https
    req.ca(this._ca)
    req.key(this._key)
    req.cert(this._cert)

    // host
    if (this._host) {
      req.set('host'.this._host)
    }

    // Save cookies when HTTP returns
    req.on('response'.this._saveCookies.bind(this))
    // The redirection saves the Cookie along with the Cookie
    req.on('redirect'.this._saveCookies.bind(this))
    req.on('redirect'.this._attachCookies.bind(this))

    // This request takes a Cookie
    this._attachCookies(req)
    this._setDefaults(req)

    return req
  }
})

export default TestAgent
Copy the code

Finally, let’s look at the code for the request function:

import methods from 'methods'
import http from 'http'
import TestAgent from './TestAgent'
import Test from './Test'

function request(app) {
  const obj = {}

  if (typeof app === 'function') {
    app = http.createServer(app)
  }

  methods.forEach(function(method) {
    obj[method] = function(url) {
      return new Test(app, method, url)
    }
  })

  obj.del = obj.delete

  return obj
}

request.agent = TestAgent

export default request
Copy the code

conclusion

Now that we’ve implemented the supertest library perfectly, let’s summarize what we’ve done:

  1. To determine therequest -> process -> expectExpect is the core of the whole test library
  2. To exposeexpectFunction to collect assertion statements, andendThe assert callback function is used to perform assertion callbacks in batches
  3. inexpectDepending on the input parameter in the function_asssertStatus 或 _assertBodyor_assertHeaderspush_assertsIn the array
  4. endFunction performsassertFunction to perform all_assertsAll assertions are called back, and network errors are handled accordingly
  5. The stack has also been modified to display errors more friendly
  6. In addition to usingrequestFunctions to test a single use case are also providedTestAgentUse case as agent test batch

The last

This is the last article in this issue of “Building the Wheel.” There are only 10 articles on “Building the Wheel.”

Although this series of articles begins with the title “Building the Wheel,” it essentially takes you through the source code step by step. Compared to the market “intensive reading source code” article, this series of articles will not come up to see the source code, but from a simple requirements to start, first to achieve a Low code to solve the problem, and then slowly optimize, and finally evolve into the source code. I hope you can take a look at the source code from shallow to deep, and there will not be too much psychological burden 🙂

Why only 10? One reason is to try out other fields and read books. Another reason is because every week to study the source code, and then start from scratch deduce the evolution of the source code is very energy consumption, really tired, afraid of the back will rot tail, with now the best state of the end.

(End loose flower 🎉🎉)