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

Pass modifier flags when triggering Buttons via keypress #561

Merged
merged 23 commits into from
Feb 1, 2017

Conversation

cmslewis
Copy link
Contributor

@cmslewis cmslewis commented Jan 27, 2017

Checklist

  • Include tests

Changes proposed in this pull request:

When we introduced AbstractButton, we unintentionally eliminated support for onKeyDown and onKeyUp callbacks on our buttons. This PR adds back that support, and it also fixes or adds tests to make sure all of this works as expected. In particular, this PR fixes an issue where a couple asserts wrapped in setTimeouts were causing button tests to pass without actually reaching those checks.

Reviewers should focus on:

  • @giladgray - I forked this off of your DRY Buttons components  #544 PR and am currently setting that as the destination branch to show just the changes I made. We should change the destination to master before merging.
  • I had to cast to any in a few spots to get around TS compiler errors. LMK if any of these castings look unacceptable.

@cmslewis cmslewis changed the title Pass modifier flags when triggering Button/AnchorButton via keypress Pass modifier flags when triggering Buttons via keypress Jan 27, 2017
@adidahiya
Copy link
Contributor

Do we really have to futz with an event's fields? Something feels off about this whole thing... AbstractButton seems to be more trouble than it's worth.

@leebyp
Copy link
Contributor

leebyp commented Jan 27, 2017

because we're using buttonRef.click() and not proxying the onClick handler, we might be limited by enzyme:
enzymejs/enzyme#566
https://github.com/airbnb/enzyme/blob/master/docs/future.md

@@ -46,6 +46,7 @@ export abstract class AbstractButton<T> extends React.Component<React.HTMLProps<
},
};

// define these as private members to avoid polluting component state with sneaky fields from this scope
Copy link
Contributor

Choose a reason for hiding this comment

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

do we still need this comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope, deleted.

@@ -59,17 +59,31 @@ function buttonTestSuite(component: React.ComponentClass<any>, tagName: string)
assert.equal(onClick.callCount, 0);
});

it("calls onClick when enter key pressed", () => {
// clewis: After adding explicit done()s that wee missing before, these
Copy link
Contributor

Choose a reason for hiding this comment

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

were missing 😭

@blueprint-bot
Copy link

Add tests to verify onKeyDown and onKeyUp triggered with modifier flags

Preview: docs | table
Coverage: core | datetime

@blueprint-bot
Copy link

Delete unneeded comment

Preview: docs | table
Coverage: core | datetime

@cmslewis
Copy link
Contributor Author

@leebyp FYI - fixed the unit-test issues with some any cleverness courtesy of @giladgray.

@cmslewis cmslewis changed the base branch from gg/refactor-buttons to master January 31, 2017 00:33
@cmslewis
Copy link
Contributor Author

@adidahiya @giladgray @leebyp - Check out the new approach (I've updated the PR description above).

@blueprint-bot
Copy link

Merge branch 'master' into cl/refactor-buttons-modifier-keys

Preview: docs
Coverage: core | datetime

// wait for the whole lifecycle to run
setTimeout(() => {
assert.equal(onClick.callCount, 1);
done();
Copy link
Contributor

Choose a reason for hiding this comment

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

im pretty sure you do, but did you check if this is still needed with the change in the logic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes.

checkKeyEventCallbackInvoked("onKeyUp", "keyup", Keys.SPACE);
});

it("calls onClick when enter key pressed", (done) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

"... when enter key released"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

// IButtonProps doesn't include onKeyDown or onKeyUp in its
// definition, even though Buttons support those props. Casting as
// `any` gets around that for the purpose of these tests.
const wrapper = button({ [callbackPropName]: callback } as any);
Copy link
Contributor

Choose a reason for hiding this comment

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

you can edit the button helper function to use IButtonProps & ButtonHTMLProps (I cant remember the exact nomenclature)

Copy link
Contributor Author

@cmslewis cmslewis Jan 31, 2017

Choose a reason for hiding this comment

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

Trying it - it's leading to a soupy syntactic mess that'll percolate throughout the file. Okay if I leave as is?

Copy link
Contributor

Choose a reason for hiding this comment

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

ouch, k

@@ -75,22 +75,24 @@ export abstract class AbstractButton<T> extends React.Component<React.HTMLProps<
};
}

protected handleKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
protected handleKeyDown = (e: React.KeyboardEvent<any>) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

why does this need to be any?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

// CC @giladgray, who looked at this with me before committing

I kept seeing compiler errors to the effect of KeyboardEvent<T> is not a supertype of KeyboardEvent<HTMLElement> for the safeInvoke line below. If there's a better way to address that issue, let me know. 👍

// doesn't properly propagate events on non-string refs
// (https://github.com/airbnb/enzyme/issues/566), we need to dig in
// and attach a spy to the ref's .click function ourselves.
const buttonRef = (wrapper.instance() as any).buttonRef;
Copy link
Contributor

Choose a reason for hiding this comment

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

as AbstractButton?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will try.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like a no-go. This change:

const buttonRef = (wrapper.instance() as AbstractButton<HTMLButtonElement>).buttonRef;

yields the following error:

[ts] Property 'buttonRef' is protected and only accessible within class 'AbstractButton<T>' and its subclasses.
(property) AbstractButton<HTMLButtonElement>.buttonRef: HTMLElement

@blueprint-bot
Copy link

Add clearer comments and fix wording in test case description

Preview: docs
Coverage: core | datetime

// we're casting as `any` to get around a somewhat opaque safeInvoke error
// that "Type argument candidate 'KeyboardEvent<T>' is not a valid type
// argument because it is not a supertype of candidate
// 'KeyboardEvent<HTMLElement>'."
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't need to duplicate this comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

const onClick = sinon.spy();
button({ onClick }, true).simulate("keyup", { which: Keys.SPACE });
setTimeout(() => assert.equal(onClick.callCount, 1), 0);
it("pressing enter triggers onKeyUp props with any modifier flags", () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

truthfully, these tests are not particularly useful. you're really just testing the safeInvoke line as it reuses the exact same event so of course the modifier flags come through. there's nothing special here, it's just vanilla React.

i'd be fine with removing these tests, or simplifying to one for ENTER and one for SPACE

Copy link
Contributor Author

@cmslewis cmslewis Jan 31, 2017

Choose a reason for hiding this comment

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

If these tests had been in place, the NumericInput functionality wouldn't have broken without warning. I want some kind of safeguard.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay if we leave them? I guess I'm okay with deleting the tests for onKeyUp, but I definitely want a test in place for onKeyDown, since that's what broke before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

K, deleted tests for onKeyUp.

// that it is being called. casting as `any` eliminates the button's
// knowledge of which members are public, private, or protected.
// this allows us to access the private buttonRef for the purposes
// of this test.
Copy link
Contributor

Choose a reason for hiding this comment

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

this comment feels too verbose. should be enough to say "mock the DOM click() function because enzyme only handles simulated React events"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

Copy link
Contributor

Choose a reason for hiding this comment

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

does not seem fixed in the diff

// knowledge of which members are public, private, or protected.
// this allows us to access the private buttonRef for the purposes
// of this test.
const buttonRef = (wrapper.instance() as any).buttonRef;
Copy link
Contributor

Choose a reason for hiding this comment

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

adi suggested casting to AbstractButton instead of any. does that work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not when I tried, no. Still complained that buttonRef was protected and therefore inaccessible.

Copy link
Contributor

Choose a reason for hiding this comment

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

oh of course

@blueprint-bot
Copy link

Delete comments / make them more concise

Preview: docs
Coverage: core | datetime

@blueprint-bot
Copy link

Delete tests for onKeyUp

Preview: docs
Coverage: core | datetime

// that it is being called. casting as `any` eliminates the button's
// knowledge of which members are public, private, or protected.
// this allows us to access the private buttonRef for the purposes
// of this test.
Copy link
Contributor

Choose a reason for hiding this comment

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

does not seem fixed in the diff

@cmslewis cmslewis merged commit 7415fc9 into master Feb 1, 2017
@cmslewis cmslewis deleted the cl/refactor-buttons-modifier-keys branch February 1, 2017 00:11
@giladgray giladgray mentioned this pull request Feb 3, 2017
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.

None yet

5 participants