Skip to content

Releases: TotalTechGeek/pineapple

Bun Support and Continuous Testing (v0.12.0)

19 Nov 23:45
63a0762
Compare
Choose a tag to compare

This release introduces significant improvements to Pineapple to make development & testing a better experience for all.

Support for Bun

Pineapple's internals have been reworked to make it possible to use Bun as the test runner, which has some significant advantages:

  • It transpiles every file for you, making it simpler to test your TypeScript, JSX and Flow projects.
  • Runs on a modified version of JavaScriptCore, which in some cases runs faster than V8.
  • Has a quicker cold-start time, which is useful for continuous testing.

If you pass in the --bun flag while invoking Pineapple, the framework will opt to use it over the traditional Node.js runner.

Caveat Emptor: If you're testing projects that depend heavily on Node-specific APIs, Bun may not be the ideal runner for your use-case.

Continuous Testing

Prior to this release, Pineapple was a one-shot test runner; you'd invoke the program & it'd spit out test results.

While it was certainly possible to pair Pineapple with nodemon or chokidar CLI, this would likely run every test in your project, rather than just the ones you were affecting.

Using the --watch-mode or -w flag, you can run Pineapple in continuous testing mode, which will only run tests that could be impacted by your modifications. The runner will traverse the dependency chain & deduce which tests in files downstream need to be run.

Video

https://youtu.be/fbtTFq53Kgo

Improving Snapshots & User Output (v0.10.0)

18 Oct 12:44
5a24472
Compare
Choose a tag to compare

This release is focused on providing some additional small quality of life improvements to the tool.

Better Snapshots

Prior to this release, all snapshots were captured in a global pineapple-snapshot, and while this worked, I don't believe it made reviewing particularly effective.

When snapshots are captured, it will now save in close proximity to your file that you're testing, appending a .psnap to the file name.

So if you were testing a ./src/math.js file, the snapshot will be persisted to ./src/math.js.psnap

More Hooks

This version introduces a few new hooks for test lifecycle management.

You may now use:

  • @beforeGlobal
  • @beforeEachGlobal
  • @afterGlobal
  • @afterEachGlobal

These hooks were introduced to make it easier to pair Pineapple with measurement frameworks, where you might need to reset certain fields.

Right now, the functions invoked do not receive any arguments, but this will likely be addressed in a future version.

Better Output

In certain cases, important error feedback was suppressed by the framework, thus making it difficult to rectify issues identified by the test. This feedback should no longer be suppressed.

v0.9.1

14 Sep 04:37
c206e79
Compare
Choose a tag to compare

Improving Property Based Testing (v0.9.1)

Hi all!

This release is focused on providing some small quality of life improvements to the property-based testing features within Pineapple.

There are two main additions to the technology:

Namespaces

/**
 * Creates the static values for use in various scenarios in our codebase.
 * @pineapple_define friends
 */
function define () {
  return {
    kevin: { /* ... */ },
    shane: { /* ... */ },
    emily: { /* ... */ }
  }
}

/**
 * #friends.emily, #friends.shane returns 'Battle won!'
 * #friends.shane, #friends.kevin returns 'Battle draw!'
 */
function fight (attacker, defender) {
  /* ... */
}

Namespaces might make it simpler to set up various generators & static values that you might wish to use throughout your tests.

Better Constant Detection

When you set up definitions in Pineapple, the testing framework will do its best to try to keep track of whether your "arbitrary expression" is actually constant.

This prevents a bunch of duplicate tests from taking place, particularly when it would be annoying (like in snapshots).

Previously, when one would try the following:

/**
 * @pineapple_define
 */
function define () {
  return { age: 17 }
}

/**
 * @test { name: 'Kevin', age: #age }
 * The above would not be detected as static in v0.9.0,
 * but will be in v0.9.1
 */
function setupAccount({ name, age }) {
  /* ... */
}

/**
 * @test #age returns false
 * The above will be detected as constant in both v0.9.0 and v0.9.1
 */
function isAmericanDrinkingAge (age) {
  return age >= 21
}

Pineapple would not be able to detect that the expression { name: 'Kevin', age: #age } was actually a constant expression. However, if you used #age
outside of a structure as seen in the second example, it would work!

To make developer's lives easier, Pineapple has been improved to try to do a better job of detecting
constant structures.

v0.9.0

23 May 02:04
3296137
Compare
Choose a tag to compare

Supercharging Pineapple with Property Based Testing (v0.9.0)

Hi all!

This release is focused on introducing fuzzing / property based testing to the Pineapple framework, which should make it ridiculously easy to cover a variety of test cases with simple test expressions.

Utilizing the amazing fast-check npm package, Pineapple is now able to fuzz a handful of test-cases and shrink any counter-examples down to the smallest test-case it can find to trip an error.

For example:

/**
 * Using fuzz testing, this will cover a handful of scenarios,
 * positives, negatives, zeroes
 * Without you needing to go over each example explicitly.
 * 
 * @test #integer, #integer returns @ as number
 * @test #integer, #integer returns args.0 + args.1
 * 
 * The above test is a little silly since it's embedding the   
 * same logic in the test, but demonstrates that it's possible.
 */
function add (a, b) {
    return a + b 
}

/**
 * @test #array(#integer) returns @ as number
 * @test #array(#string, { minLength: 1 }) throws
 * @test [1, 2, 3] returns 6
 * @test [#integer, 2, 3] returns args.0.0 + 5
 * @test [] returns 0
 */
export function sum (values) {
  if (values.some(i => typeof i !== 'number')) throw new Error('An item in the array is not a number.')
  return values.reduce((a, b) => a + b, 0)
}

/**
 * @test { name: #string, age: #integer(1, 20) } throws
 * @test { name: 'Jesse', age: #integer(21, 80) } returns cat(args.0.name, ' is drinking age.')
 */
export function drinkingAge ({ name, age }) {
  if (age >= 21) return `${name} is drinking age.`
  throw new Error(`${name} is not drinking age.`)
}

This works great for handling a variety of scenarios without having to write much code, and also works with the snapshot tech built into Pineapple (making it even easier to pin functionality for a handful of test-cases).

When your tests fail though, Pineapple & Fast-Check will work together to help identify the issue.

/**
 * A simple template function.
 * @test 'Hello $0' ~> #string returns cat('Hello ', args.0)
 * @param {string} templateString
 */
export function template (templateString) {
  /** @param {string} replace */
  return replace => templateString.replace(/\$0/g, replace)
}
✖ Failed test (template): 'Hello $0' ~> #string returns cat('Hello ', args.0)
>> file:///Users/jesse/Documents/Projects/pineapple/test/fuzz.js:35
- Expected
+ Received

- Hello $$
+ Hello $
Failing Example: [
  "$$"
]
Shrunk 4 times.
Seed: -2121637705

Fast-Check shrinks the test-case to help you as the developer realize: "Oh! The replace string needs escaped because the $ character is special in the replace function."

v0.8.0

19 May 03:32
8a8112b
Compare
Choose a tag to compare

Extending Interchange Formats & Subset Testing (v0.8.0)

Hello!

This patch introduces the ability to select your output format, which should help with editor integrations in the future.

If you use OUTPUT_FORMAT=JSON or -f json, you are able to have Pineapple output to an ndjson stream which should be more easily parsable by a program.

For reference:

✔ Passed test (fib): 1
✔ Passed test (fib): 3
✔ Passed test (fib): 10
✔ Passed test (add): 1, 2
✔ Passed test (add): '4', 3 throws
✔ Passed test (add): 1, '0' throws
✔ Passed test (add): -1, 1
✔ Passed test (add): -1, 1 to 0
✖ Failed test (add): -1, 1 to -1

Will become the following in JSON mode:

{"type":"Success","name":"fib","input":"1","file":"file:///Users/jesse/Documents/Projects/pineapple/test/math.js:39"}
{"type":"Success","name":"fib","input":"3","file":"file:///Users/jesse/Documents/Projects/pineapple/test/math.js:40"}
{"type":"Success","name":"fib","input":"10","file":"file:///Users/jesse/Documents/Projects/pineapple/test/math.js:41"}
{"type":"Success","name":"add","input":"1, 2","file":"file:///Users/jesse/Documents/Projects/pineapple/test/math.js:2"}
{"type":"Success","name":"add","input":"'4', 3 throws","file":"file:///Users/jesse/Documents/Projects/pineapple/test/math.js:3"}
{"type":"Success","name":"add","input":"1, '0' throws","file":"file:///Users/jesse/Documents/Projects/pineapple/test/math.js:4"}
{"type":"Success","name":"add","input":"-1, 1","file":"file:///Users/jesse/Documents/Projects/pineapple/test/math.js:5"}
{"type":"Success","name":"add","input":"-1, 1 to 0","file":"file:///Users/jesse/Documents/Projects/pineapple/test/math.js:6"}
{"type":"Failure","name":"add","input":"-1, 1 to -1","message":"- Expected\n+ Received\n\n- -1\n+ 0","file":"file:///Users/jesse/Documents/Projects/pineapple/test/math.js:7"

This release also introduces the ability to run a subset of tests using the --only flag.

Additionally, it adds the file name & line number to failed test output (even in pretty mode), which should make it easier to jump to your test cases.

v0.7.0

17 May 21:06
1081572
Compare
Choose a tag to compare

Minor Patch: v0.7.0

Hi all!

This minor patch improves the developer experience around snapshots by making the output readable (as opposed to the Jest Serialization mechanism that it used in previous versions).

{
  "fib(1) [dRX81e0Zt9zxfAdy4cKtrrKMfyO/nvL9WF+XRAOtEB0=]": {
    "value": 1,
    "async": false
  },
  "fib(3) [KTjgP0vq5dR61BJFF+PbmmL0idLvto8mYF5cAbndz5k=]": {
    "value": 2,
    "async": false
  },
  "fib(10) [RDou6nU/Mgg9Olsl1Kd1FGLxi1Ij/V+3bw0spgCqCnY=]": {
    "value": 55,
    "async": false
  },
  "add(1, 2) [O6M1izKkUUPb7fRhfnhMZ8VxO25LxM0bS6rw/tGm5YA=]": {
    "value": 3,
    "async": false
  },
  "add(-1, 1) [hnYzkbZiJjMD0YnEHZer8Pwyyf32Pd3dus2/O70SBZk=]": {
    "value": 0,
    "async": false
  },
  "mul(3, 5) [3uLRCxaVjev70tv9IFOlLrFQMM2wYWl0A1q5WwoopjE=]": {
    "value": 15,
    "async": false
  }
}

This should make it simpler to review snapshots for the purposes of pull-requests.

The syntax is json-like, in that it actually uses Pineapple's grammar & functions to parse it, which will make it easier to support things like dates & bigints, or other types of values later on.

{
    "addAsync(5n, 3n) [X76+w3gcfI4QVFELW0Sgv2OKYXurpbbu3cu+5ki2IfM=]": {
        "value": 8n,
        "async": true
    }
}

v0.6.4

11 May 13:03
3159da1
Compare
Choose a tag to compare

v0.6.4

This is an extremely minor patch that includes some styling / code cleanups.

v.0.6.3

10 May 16:15
f6f510e
Compare
Choose a tag to compare

Minor Patch: v0.6.3

This patch introduces a small quality of life improvement, which I felt was particularly necessary after introducing class-based testing:

Multiline Test Cases!

/**
 * @test 'Jesse', 24 
 * ~> $.grow(3)
 * ~> $.grow(2) returns 29
 * 
 * @test 'Rick', 62 
 * ~> $.grow(1) returns 63
 * ~> $.grow(2) returns 65
 * ~> $.getName() returns 'Rick'
 * ~> $.grow() returns $.age === 66
 */
export class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }

    grow(amount = 1) {
        return this.age += amount
    }

    getName() {
        return this.name
    }
}

If you write a test case on multiple lines, Pineapple will now automatically concatenate it to the test case. This is not exclusive to class / higher-order function syntax.

/**
 * @test { 
 *      tenant: 'Rick',
 *      length: 10,
 *      type: 'boat' 
 * } resolves
 * 
 * @test {
 *      tenant: 10,
 *      length: 'Rick',
 *      type: 'boat'
 * } rejects
 */
export async function createLease({ tenant, length, type = 'boat' }) {
    if (typeof tenant !== 'string' || typeof length !== 'number') 
        throw new Error('Types do not match.')
    return { type, tenant, length }
}