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

Spike: Camel case syntax #784

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft

Spike: Camel case syntax #784

wants to merge 19 commits into from

Conversation

papandreou
Copy link
Member

@papandreou papandreou commented Dec 23, 2020

This is a super inefficient proof-of-concept implementation so that we can discuss the syntax etc.

If we can achieve some kind of consensus, I promise to clean it up and optimize it :)

  • Work through edge cases discovered by auto-converting the test suite to camel case: 74903a4
  • Think about how we might capture the full context in middle-rocket cases like expect([1, 2,3]).whenPassedAsParametersTo(Math.max).toEqual(4) -- right now that just fails with expected 3 to equal 4 here.
  • Think about flag forwarding again (https://gitter.im/unexpectedjs/unexpected?at=5ce1417463dea422b4abaa6c)
  • Figure out what expect.it(fn) should map to.

@papandreou papandreou self-assigned this Dec 23, 2020
@alexjeffburke
Copy link
Member

I took a look at this PR - some thoughts follow.

Implementation wise I think it is very straightforward - one thing I learnt from trying unassessed was that we may may not want to expose all the assertions like this. In it I had somewhat of a whitelist, although it was implemented by looking at the assertion string / type information and at least filtering out any that were middle-rocket.

On a perhaps more controversial point, I can't help but be in two minds about whether this should really be in unexpected core. It almost feels like it's might be something else if we expose the methods like this. From what I can tell, we can almost do this by making use of being able to hook unexpected - perhaps we should consider making that infrastructure work and putting this out under a new name?

if (typeof format === 'undefined') {
return this._outputFormat;
} else {
this._assertTopLevelExpect();
Copy link
Member

Choose a reason for hiding this comment

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

IIRC I hit an issue with this myself at some point and I think this fix may want to be applied directly to master.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I agree: 5349b0fa

if (args.length < 1) {
throw new Error('The expect function requires at least one parameter.');
} else if (args.length === 1) {
return this._camelCaser(context, this.findTypeOf(subject), subject);
Copy link
Member

@alexjeffburke alexjeffburke Jan 4, 2021

Choose a reason for hiding this comment

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

Re the latter part of my comment here, it's weakening the two parameter rule and making a single argument suddenly make the library behave very differently that got me pondering whether this bends things too far.

Copy link
Member Author

@papandreou papandreou Jan 4, 2021

Choose a reason for hiding this comment

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

I don't think it's too bad -- it actually aligns the one parameter expect case with what the nested expect made available to assertion rule handlers does:

throw new Error('The expect function requires at least one parameter.');

... And if we agree about a long term goal of deprecating/removing the old string-based syntax, I think we can live with a bit of temporary overloading.

@papandreou
Copy link
Member Author

Implementation wise I think it is very straightforward - one thing I learnt from trying unassessed was that we may may not want to expose all the assertions like this. In it I had somewhat of a whitelist, although it was implemented by looking at the assertion string / type information and at least filtering out any that were middle-rocket.

This implementation preserves middle rocket support via this syntax:

expect([1, 2, 3]).whenPassedAsParametersTo(Math.max).toEqual(3);

Then we can have a separate conversation about whether to unsupport middle rocket altogether. Last time we talked about it, I remember that we were a bit back and forth about whether to part with it, and that especially unexpected-dom had some very attractive use cases for it.

We've also talked about requiring middle rocket assertions to declare the type of what they're expect.shifting, which has the potential for us to produce more precise typings. I would prefer to have a separate discussion about that, though.

On a perhaps more controversial point, I can't help but be in two minds about whether this should really be in unexpected core. It almost feels like it's might be something else if we expose the methods like this. From what I can tell, we can almost do this by making use of being able to hook unexpected

In my opinion the camel case syntax is superior to what we have now (and less controversial), so I think we should go all in on it, switch all the docs over to use it, and aim to remove the current assertion syntax. If we hook it in and keep it in a plugin, we'll be sending mixed signals and end up with a fragmented ecosystem.

perhaps we should consider making that infrastructure work and putting this out under a new name?

That's called unassessed, isn't it? 😼

In my opinion we've essentially already done that and proven that it's pleasant to work with, so I think now's the time to put it in core.

it('should fail', () => {
expect(() =>
expect({ foo: 123 }).toSatisfy({
foo: expect.toBeANumber().andToBeGreaterThan(200),
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we could still attach these to a .it object and simply hand one out that has had the camelCaser run over it when we're in that mode?

Reason this occurs to me is that I think the .it. still serves a purpose syntactically in saying "and use the subject from the situation in which you are called.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I was a bit back and forth on that, decided that it looked fairly clean without any it. Could you spell out with some examples exactly which alternative(s) you're thinking about? expect.it.toBeANumber()? To me that reminded me too much of chai's weirdness where the dot notation and camel case are mixed -- expect(foo).to.shallowDeepEqual(bar) 🙀

Copy link
Member

Choose a reason for hiding this comment

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

Hmm I see what you're getting at. Let me sit with this some more...

More thinking aloud, for the purposes of discussion, what do we do with the value returning form of expect.it? Perhaps the normal assertions should be callable directly, but we provide a .it() that is specifically meant to be used only as .it(value => { ... }) ?

Copy link
Member Author

@papandreou papandreou Jan 4, 2021

Choose a reason for hiding this comment

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

Hmm, yeah, the expect.it(fn) case is interesting to consider. It shouldn't really be a problem to keep supporting it as-is.

You could argue that if we're going to migrate it over to something that's not backwards compatible (or if we want to add another variant of it that's easier on the eyes in a camel case context), we might as well switch to something completely different that isn't necessarily an overloading of the expect.it replacement.

).toThrow(
'expected { foo: 123 } to satisfy\n' +
'{\n' +
" foo: expect.it('to be a number')\n" +
Copy link
Member

Choose a reason for hiding this comment

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

As we proceed to making this user-ready we probably need to consider what to do here - perhaps we could switch out the expect.it type such that this would render the camel cases names? Wondering if that can fit with my comment about about keeping the .it. above as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, absolutely, that would be fairly easy. We can try to make the rendering respect the syntax that was actually used in the source code, or just make an unconditional switch to camel case.

describe('and extra chaining with .or...', () => {
it('should succeed', () => {
expect({ foo: 123 }).toSatisfy({
foo: expect.toBeAString().orToBeGreaterThan(100),
Copy link
Member

Choose a reason for hiding this comment

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

Im not too keen of having different spelling of the assertions like this - I think that would get confusing when it came to documentation and the like.

Thinking aloud were ok chaining through a .or. we could potentially generate the object with all these assertions only once.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's fair, we have a bunch of wiggle room wrt. the syntax here. Again, I'm not too keen on mixing dot notation and camel case, but something like expect.toBeAString().or().toBeGreaterThan(100) could be arranged?

Copy link
Member

Choose a reason for hiding this comment

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

That is far better to my eyes at least!

@papandreou papandreou changed the base branch from master to feature/childInheritsFromParentExpect April 25, 2022 19:44
@papandreou papandreou force-pushed the feature/childInheritsFromParentExpect branch from 23105f0 to 16e4b28 Compare April 25, 2022 19:46
Base automatically changed from feature/childInheritsFromParentExpect to master April 26, 2022 18:45
@papandreou papandreou force-pushed the feature/camelCase branch from 66eac00 to 7b0eb42 Compare May 28, 2022 22:20
@papandreou papandreou force-pushed the feature/camelCase branch from ed68984 to a3b3ee0 Compare May 31, 2022 05:47
@pingvinen
Copy link

In my opinion the camel case syntax is superior to what we have now (and less controversial)

In my opinion the current assertion syntax is far superior to this DSL-chaining thing. It is one of the primary reasons why I use unexpected in the first place. If unexpected is used in the "exact" same way as the rest of the assertion frameworks, then why do we need unexpected at all?

@papandreou
Copy link
Member Author

Hopefully because of the extensibility and quality of the output when an assertion fails? :)

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

Successfully merging this pull request may close these issues.

3 participants