Skip to main content

7 posts tagged with "patch"

View All Tags

· 2 min read
Jesse Mitchell

Hey there!

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.

An example of the snapshot functionality where the code is modified and the snapshot fails due to a renamed attribute

Video

· One min read
Jesse Mitchell

Hey everyone!

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.

· 2 min read
Jesse Mitchell

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.

· 3 min read
Jesse Mitchell

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."

If you wish to read up more on the Fuzz Testing technology, you may do so here.

· One min read
Jesse Mitchell

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.

· One min read
Jesse Mitchell

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
}
}

· One min read
Jesse Mitchell

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 }
}