Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add truthy/falsy alias #663

Closed
wants to merge 1 commit into from

Conversation

kentcdodds
Copy link
Contributor

Happy to make any changes you'd like. I'd just like to get the discussion going about this :D

@mention-bot
Copy link

By analyzing the blame information on this pull request, we identified @kasperlewau, @sotojuan and @SamVerschueren to be potential reviewers

@kentcdodds
Copy link
Contributor Author

Sorry, here's an example of my use case:

test('passes with a relative path', t => {
  const output = {filename: relativePath}
  const result = validate(output.filename)
  t.notOk(result)
})

test('returns error if given an absolute path', t => {
  const output = {filename: absolutePath}
  const result = validate(output.filename)
  t.ok(result)
})

In this scenario, it "passes" if it's notOk which is counter intuitive. Much easier to read this as: t.falsy(result) (note: result can be null or undefined and both are ok in this context).

@kentcdodds kentcdodds force-pushed the pr/truthy-falsy-alias branch from ce6a8a4 to a52ffd0 Compare March 21, 2016 19:21
@kentcdodds
Copy link
Contributor Author

Build broke because of linting. Sorry, I'm a creature of habit (http://kcd.im/semicolons) :-) I've force pushed a fix.

@sindresorhus
Copy link
Member

We have intentionally not added any aliases (there are a lot of them in tape for example). It might seem nice at first, but it causes a lot of overhead for the user and inconsistency. Now a team needs to decide which alias to use and in what situations, and enforce that consistently. It goes against the AVA mantra (stolen from Python) of having preferably one way to do something. Instead, I would recommend taking advantage of power-assert and just do t.ok(result == null);. That's even clearer than the word truthy, which many don't really know what means exactly anyways.

@vdemedes @novemberborn @jamestalmage Thoughts?

@kentcdodds
Copy link
Contributor Author

Totally understand the value of avoiding aliases and respect if it's decided to avoid them 👍

@kentcdodds
Copy link
Contributor Author

Opened another PR to add a note to the CONTRIBUTING.md.

@jamestalmage
Copy link
Contributor

I wish the functions were called truthy and falsy, I have never liked ok/notOk.

If we were to alias them, I would want to immediately deprecate ok and notOk and remove them at some later date.

Truth be told, I wish a number of our assertion methods were named differently (i.e. It is not possible for you to know what same means without documentation, deepEqual would have been better IMO). Still, I think it is better to keep only one way to do it then make aliases.

Fact of the matter is, it took me just a few hours to commit AVA's limited list of assertions to memory, and now when I look at other peoples AVA tests I know what they mean without pulling up the docs. node-tap has a long list of aliases, and there has been more than one occasion where I've reviewed a test written for tap and needed to pull up docs to verify what I was seeing was an alias, and not a custom assertion.

Also, I think there is value in supporting custom assertions someday, and every alias we add removes another name from the namespace.

@kentcdodds
Copy link
Contributor Author

If we were to alias them, I would want to immediately deprecate ok and notOk and remove them at some later date.

It is not possible for you to know what same means without documentation, deepEqual would have been better IMO

I realize that there's a community with code that depends on these things, but I think that as a pre-1.0.0 project, there's a responsibility to get the API correct and obvious, even at the cost of breaking changes. If it's really that bad, we could write a codemod that people could run on their code.

it took me just a few hours to commit AVA's limited list of assertions to memory

I think this is awesome. It would be even more awesome if learning what these assertions do doesn't require a docs lookup. I feel like ok, notOk, and same are examples of things that are easy to learn, but require a docs lookup :-(

I'd be happy to make the PR to alias things and add a deprecation message.

@jamestalmage
Copy link
Contributor

@kentcdodds - I personally like that plan. But I would wait for other maintainers to chime in before proceeding.

The codemod would be a must IMO. Maybe we could even ship the codemod with AVA for a while and offer a CLI prompt to apply it automatically. We already know which files are the test files from the config, so we could make it pretty easy for users. (I think deciding whether or not to ship it with AVA depends on how many dependencies the codemod would add).

@novemberborn
Copy link
Member

Instead, I would recommend taking advantage of power-assert and just do t.ok(result == null);. That's even clearer than the word truthy, which many don't really know what means exactly anyways.

Major +1 on promoting power-assert. If only I could do t(result === null) 😉

I wish the functions were called truthy and falsy, I have never liked ok/notOk.

"Okay" doesn't feel like an absolutist assertion. Seems on-par with truthy/falsy (and I never know how to spell falsey).

Similarly same feels sufficiently hand-wavy for a deep equals, with less typing required.

The codemod would be a must IMO. Maybe we could even ship the codemod with AVA for a while and offer a CLI prompt to apply it automatically. We already know which files are the test files from the config, so we could make it pretty easy for users. (I think deciding whether or not to ship it with AVA depends on how many dependencies the codemod would add).

Would be good to have a codemod for breaking changes like this, but let's keep it as a separate package. Could implement a stub assertion that fails the test with a message pointing towards the codemod. If we ever get to that point.

@jamestalmage
Copy link
Contributor

You can google truthy, googling ok is useless. You need tap/tape experience to know what ok means, you only need sufficient javascript experience to immediately understand what truthy means.

My problem with same is the ambiguity. It took me a long time to stop thinking it meant "the same instance", as in ===.

@kentcdodds
Copy link
Contributor Author

My problem with same is the ambiguity. It took me a long time to stop thinking it meant "the same instance", as in ===.

I've had the same experience.

I don't want to bikeshed on stuff, and (so far) it looks like this is pretty subjective anyway :-)

@novemberborn
Copy link
Member

Not opposed to changing this but I don't necessarily feel the need either ¯_(ツ)_/¯

@sindresorhus
Copy link
Member

I think ok and truthy are equally bad. truthy is also longer to type and can easily be confused with true when skimming assertions. I added ok initially since tape had it and it's useful for just checking that a property exists t.ok(obj.prop) or that a function returns something t.ok(fn()).

@vadimdemedes
Copy link
Contributor

Personally, I'm fine with ok and notOk, although I do understand that truthy/falsy would probably be quicker to understand for newcomers.

As I see, our opinions on this are separated, so would be good to get more input from AVA users.

/cc @sotojuan @twada @floatdrop @ariporad @kasperlewau @BarryThePenguin @jokeyrhyme @naptowncode @spudly

@kentcdodds
Copy link
Contributor Author

If we're going to get people's opinions (this is great). I propose that we just use GitHub issue reactions to do a voting system.

👍 this comment if you're in favor of changing ok and notOk (to anything)

👎 this comment if you'd prefer to keep ok and notOk as they are.

😕 if you don't care either way.

@sotojuan
Copy link
Contributor

I'm with you but I think I am biased—ok sounds fine to me but that may be because I've used it for months now. A beginner's perspective may be better. I think @kentcdodds did a Twitter poll about this, or if he hasn't maybe he should.

That said, I think truthy shows intent and meaning way more directly. Sort of how throws tells you all you need to know from the name. But it is longer and more annoying to type. Hard choice!

If we do make a change I think it'd be better to soft deprecate for now till 1.0 (ok still works but it's undocumented) and see what people think.

@kentcdodds
Copy link
Contributor Author

I think @kentcdodds did a Twitter poll about this, or if he hasn't maybe he should.

FWIW, here are the results ¯\_(ツ)_/¯

screen shot 2016-03-24 at 9 16 00 am

But it is longer and more annoying to type.

For me this shouldn't make a difference. Readability/maintainability should go before how many characters there are IMO ¯\_(ツ)_/¯

If we do make a change I think it'd be better to soft deprecate for now till 1.0 (ok still works but it's undocumented) and see what people think.

👍

@spudly
Copy link
Contributor

spudly commented Mar 24, 2016

I wasn't going to spam everyone with a +1 (no need these days), but since I was specifically invited...

would be good to get more input from AVA users.

I've always hated ok() and notOk(). I think the intent is not clear in the function name. Sometimes I want to assert that a property exists. Sometimes I want to assert that it is defined. Sometimes I want to assert that it is truthy.

I'm all for adding t.truthy and t.falsy and deprecating ok() and notOk() but if we do so I think we should also add some other semantically named functions because t.truthy and t.falsy are only two of many use cases.

@spudly
Copy link
Contributor

spudly commented Mar 24, 2016

... also if the recommended is always going to be to lean on powerassert, then I think that t.assert(condition) or t(condition) would be preferrable to t.ok(condition)

@sindresorhus
Copy link
Member

... also if the recommended is always going to be to lean on powerassert

t.true(condition) is what you want in most cases.

@spudly
Copy link
Contributor

spudly commented Mar 25, 2016

I'm all for adding t.truthy and t.falsy and deprecating ok() and notOk() but if we do so I think we should also add some other semantically named functions because t.truthy and t.falsy are only two of many use cases.

I take this back. I've been thinking a lot about this because I'm refactoring tons of mocha tests to be ava tests and I've decided that I was wrong. I think instead of adding truthy/falsy, we should just replace all of the assertion functions with one single function that fails the test if the provided value is falsy.

The following would all be deprecated (recommended replacement on the right):

t.pass([message])                              // t.assert(true, [message])
t.fail([message])                              // t.assert(false, [message])
t.ok(value, [message])                         // t.assert(value, [message])
t.notOk(value, [message])                      // t.assert(!value, [message])
t.true(value, [message])                       // t.assert(value === true, [message])
t.false(value, [message])                      // t.assert(value === false, [message])
t.is(value, expected, [message])               // t.assert(value === expected, [message])
t.not(value, expected, [message])              // t.assert(value !== expected, [message])
t.same(value, expected, [message])             // t.assert(t.deepEqual(value, expected, [message]))
t.notSame(value, expected, [message])          // t.assert(!t.deepEqual(value, expected, [message]))
t.throws(function|promise, [error, [message]]) // t.assert(t.throws(function|promise, [error, [message]]))
t.notThrows(function|promise, [message])       // t.assert(!t.throws(function|promise, [error, [message]]))
t.regex(contents, regex, [message)             // t.assert(regex.test(contents), [message])
t.ifError(error, [message])                    // t.assert(!error, [message])

We'd still have to have deepEquals or throws functions, but they wouldn't be assertions. They could just return true/false. The single assertion function could be t.assert(), t.ok(), or just t(). Doesn't matter to me.

@kasperlewau
Copy link
Contributor

I'll chime in with my some thoughts on the subject.

I try to stay away from truthy/falsy as much as possible, as I never thought either were clear enough to be fitting in a unit test. And coming from a Mocha background, I never really got deep into ok/notOk.

I added ok initially since tape had it and it's useful for just checking that a property exists t.ok(obj.prop) or that a function returns something t.ok(fn()).

t.true(obj.hasOwnProperty('foo'));
t.true('foo' in obj);
t.true(fn() !== undefined);

What's of importance to me when writing unit tests, is for my peers and coworkers to be able to look at the tests and just get it at first glance, without having to sift through docs.

A lot of us are working on pet projects outside of our daily jobs, and the amount of API surface that you have to get comfortable with nowadays is quite vast.

I think the smaller the API surface of ava, the less assertions methods available - the better. That brings us closer to the 'common ground' that is JS which assists in context switching between different projects and stacks, I think.

Kill' em all I say - I don't see myself using either truthy/falsy/ok/notOk to the extent that it would warrant their existence. I quite like @spudly's latest suggestion 😄


Truth be told, I wish a number of our assertion methods were named differently (i.e. It is not possible for you to know what same means without documentation, deepEqual would have been better IMO). Still, I think it is better to keep only one way to do it then make aliases.

My problem with same is the ambiguity. It took me a long time to stop thinking it meant "the same instance", as in ===.

I'm in full agreement here. I really didn't grok same right off the bat which stumped me a fair bit. I (very briefly) touched on that in one of my previous PR's - it just feels a little far fetched and arguably too open to personal interpretation. Whereas I think deepEqual would be straight to the point.

Not wanting to introduce aliases, I'd support a s/same/deepEqual change.

@jamestalmage
Copy link
Contributor

IMO, the value of ok / notOk is for methods that might return a falsy value. Imagine a "safe" fs.readFileSync that returned null or undefined instead of throwing "file not found" errors. That is where I might use these to assert "I got a value" vs. "I didn't". The right answer might be to always return null and do t.is(x, null). But that sometimes requires coercing undefined to null in situations where it really isn't really important to do so. Still, I am willing to be convinced to drop them, though I'm skeptical.

falsy/truthy is pretty well defined. There are Wikipedia entries on both easily found by googling. Googling ok is not helpful. So I prefer those names if these assertions stick around.

@sindresorhus
Copy link
Member

Not wanting to introduce aliases, I'd support a s/same/deepEqual change.

Yeah. I kinda regret that one now. It sounded clear when we added it. t.same means it's the same structure, while t.is, means it is the same thing. I do wonder if deepEqual is clearer because you've heard it before, rather than actually being clearer.

falsy/truthy is pretty well defined. There are Wikipedia entries on both easily found by googling. Googling ok is not helpful. So I prefer those names if these assertions stick around.

But if neither is clear enough by the code itself, it's doesn't really matter if one is more Google-able. The documentation would cover either.

@jamestalmage
Copy link
Contributor

truthy vs ok:

I would argue you have at least a chance of understanding the intent of the truthy assertion using only knowledge of the Javascript language. truthy is probably the most commonly used word to describe JavaScripts treatment of non-booleans in conditional statements. Actually, if there are other terms besides truthy/falsy, I don't know them.

If I was describing some JS algorithm in English, I would never use the phrase "If the returned value is ok, then do ...". It's just too ambiguous, what does ok mean? I could imagine it meaning lots of things: "properly formatted", "not an Error", "not a rejected promise", etc. I think the only way you see t.ok and immediately understand what it means is if you are familiar with assertion libraries that use the convention.

So, that is my best argument for truthy/falsy. The ability to understand with basic Javascript knowledge vs. needing to have experience with certain assertion libraries. Neither is perfect, but I do think truthy/falsy is superior.


same vs deepEqual:

I do like deepEqual better, but only because I think same is problematic. I come from a Java background, and in that world, most same assertions mean "same instance". I probably did have to look up what assert.deepEqual did the first time I saw it (I don't remember). It certainly feels more descriptive, and I don't think you are as likely to confuse it with an entirely different behavior.


Ultimately, I am willing to live with whatever we decide, but I do think this line from @kentcdodds bears repeating:

I think that as a pre-1.0.0 project, there's a responsibility to get the API correct and obvious, even at the cost of breaking changes

@kasperlewau
Copy link
Contributor

I do wonder if deepEqual is clearer because you've heard it before, rather than actually being clearer.

I suppose there's some merit to the notion that deepEqual is clearer because I've heard of it and used it before. I do think however that deepEqual in no way indicates that we're dealing with the same entity, whereas same for obvious reasons could potentially indicate that.

The issue as I see it regarding same is that it doesn't really do a good job indicating that the structure is the same between the two. Nor does it convey that we're dealing with the same instance. It's just not explicit enough for my taste.

If you wanted to be really explicit about it and communicate a (to me) crystal clear intent (which I think trumps character count any day of the week when it comes to writing tests), t.sameStructure could be something to look into.

It's not something I've ever come across before, and there's probably good reasons for that - but I think it does tick off some boxes:

  • It doesn't involve equal, so there's no risk of having a mix up in regards to the differences between equal and deepEqual.
    • This point is sort of moot as there's a clear enough distinction between equal and deepEqual. same has no 'partner', which could contribute to the confusion caused.
  • It is explicit in what we're expecting - the same structure of the given entities.
  • It's not implicit like same, so we're not running the risk of someone misinterpreting it as ===.

I'll end this comment off by saying I'm happy the discussion is alive and kicking, but would it make sense to move it out of the PR? It feels as though we're drifting too far out into off topic land and not discussing the code at hand. Sorry about that.

@jamestalmage
Copy link
Contributor

would it make sense to move it out of the PR?

Meh. I think @kentcdodds was really after a discussion more than getting this merged.

@kasperlewau
Copy link
Contributor

Fair enough :)

@kentcdodds
Copy link
Contributor Author

It's just too ambiguous, what does ok mean? I could imagine it meaning lots of things: "properly formatted", "not an Error", "not a rejected promise", etc.

If you all recall, this is the precise reason that I created this in the first place:

In this scenario, it "passes" if it's notOk which is counter intuitive. Much easier to read this as: t.falsy(result) (note: result can be null or undefined and both are ok in this context).

The intuition of an API is hard to get right for all use cases, but I think that we can all agree that there are more cases where falsy makes more sense and is more explicit than notOk.

On deepEqual vs same: I'm a big fan of this change. I think that deepEqual is common enough that people will get it. If we want to go for a name that people don't have to google to understand then: primitiveValuesAreTheSame might do, but that's just crazy talk... I think deepEqual's great.

I think @kentcdodds was really after a discussion more than getting this merged.

Yes, this is true. But I think we've reached a point where we need to start seeing and talking about code (even if it's not quite agreed upon yet, the PR doesn't have to be merged 😄).

Either way, this PR will not be merged, so I'll go ahead and close it :-)

@kentcdodds kentcdodds closed this Mar 27, 2016
@kentcdodds kentcdodds deleted the pr/truthy-falsy-alias branch March 27, 2016 03:06
@@ -30,6 +30,8 @@ test('.ok()', function (t) {
assert.ok(true);
});

t.same(assert.ok, assert.truthy);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed that even in this very PR it is evident that I confused .same() with .is() :-)

@novemberborn
Copy link
Member

@jamestalmage

I would argue you have at least a chance of understanding the intent of the truthy assertion using only knowledge of the Javascript language. truthy is probably the most commonly used word to describe JavaScripts treatment of non-booleans in conditional statements. Actually, if there are other terms besides truthy/falsy, I don't know them.

If I was describing some JS algorithm in English, I would never use the phrase "If the returned value is ok, then do ...". It's just too ambiguous, what does ok mean? I could imagine it meaning lots of things: "properly formatted", "not an Error", "not a rejected promise", etc. I think the only way you see t.ok and immediately understand what it means is if you are familiar with assertion libraries that use the convention.

So, that is my best argument for truthy/falsy. The ability to understand with basic Javascript knowledge vs. needing to have experience with certain assertion libraries. Neither is perfect, but I do think truthy/falsy is superior.

Well that convinced me. +1 for changing to t.truthy()/t.falsy().

@jokeyrhyme
Copy link
Contributor

Another approach might be to drop / deprecate all truthy / falsey behaviour and encourage developers to use strictly boolean expressions.

@kentcdodds
Copy link
Contributor Author

Another approach might be to drop / deprecate all truthy / falsey behaviour and encourage developers to use strictly boolean expressions.

I'd be fine with that as long as we provide a list of good utility libraries for doing common test cases (like deepEqual for instance).

@novemberborn
Copy link
Member

I'd be fine with that as long as we provide a list of good utility libraries for doing common test cases (like deepEqual for instance).

I think those batteries should come included.

@kentcdodds
Copy link
Contributor Author

I think those batteries should come included.

Even better

@novemberborn
Copy link
Member

Even better

Hence t.same() 😉

@vadimdemedes
Copy link
Contributor

I'd be sad to see t.same() go :( deepEqual is way longer, but if that's clearer to AVA users, let's do it.

@novemberborn now I'm convinced we should migrate to truthy/falsy too (#663 (comment)).

@sindresorhus
Copy link
Member

I'd be sad to see t.same() go :( deepEqual is way longer

Use the editor snippets. They autocomplete ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants