-
Notifications
You must be signed in to change notification settings - Fork 466
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
Proposal setup to make the TypeScript migration easier #614
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 3e8c451:
|
I'm not sure to understand why the build fails. The "typecheck" step works for me :-/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the reason it's working on your machine and not on CI is a version mis-match. I think fixing the error should be pretty straightforward though.
This is looking pretty good. Could you try and move one other file to TypeScript so we get a more realistic view of what this would be like?
Disable some tslint rules which conflicts with the declarations generated by tsc Also remove the useless babel.config.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feeling pretty comfortable with this generally. I'm open to seeing what this looks like if the repo is 100% TypeScript.
types/event-map.d.ts
Outdated
@@ -0,0 +1,72 @@ | |||
declare type EventMapKey = string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are .d.ts
files necessary when the source is written in TS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the migration will be over, the types
directory can be removed from the source; the declaration files will be generated during the build phase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we not start generating the types before everything is migrated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure. tsc
uses the typescript source files to extract the declarations. i have no idea how to do that before everything is migrated. If someone knows a way i'd be very glad to try it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% sure I understand what you're wanting to do but tsc only runs on typescript files so if you want it to generate typescript definition files for only those that have been moved over that should happen naturally. I'm sure we could find what you're looking for in the compiler docs. But if you clarify what you are looking to do I could do more research.
types/get-node-text.d.ts
Outdated
@@ -1 +1,2 @@ | |||
export function getNodeText(node: HTMLElement): string; | |||
declare function getNodeText(node: HTMLElement): string; | |||
export { getNodeText }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. Do we need .d.ts
files when the source is in TypeScript?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't need to. Might need to add the -d
flag when compiling or set that in your tsconfig.
It seems the prettier settings can conflit with the 'whitespace' rule of dtslint. Also forces the dtslint version to be sure Travis use a version with this fix: microsoft/dtslint#295
Codecov Report
@@ Coverage Diff @@
## master #614 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 24 24
Lines 555 559 +4
Branches 138 141 +3
=========================================
+ Hits 555 559 +4
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great. I'm happy to merge this as-is and then we can work on converting the rest over. But I'd like another 👍 from someone before I do.
types/events.d.ts
Outdated
@@ -1,95 +1,104 @@ | |||
export type EventType = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we use the native HTMLElementEventMap
or WindowEventMap
instead of maintaining a custom type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't know this existed. I'd prefer this 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, addEventListener
is typed, so my first thought is that we should be able to reuse those types.
// HTMLDocument
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
// HTMLElement
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
@ghostd, please correct me if I'm wrong. I might miss something fundamental.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my understanding, removing the custom EventType
will not prevent us to maintain the event map (since the metadata are 'erased' at runtime), and for now, the lib.dom.ts is still missing some events (composition ones and maybe others).
Maybe we can have a custom type which would be a union of the DocumentEventMap
, HTMLElementEventMap
and the missing events used by dom-testing-library
. In this way we will just maintain the missing types. I'll give a shot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think i misunderstood your point.
We can simply define EventType
this way:
type EventType = keyof (typeof eventMap);
When we add an entry into eventMap
, TS will infer the new definition of EventType
Does this answer to your point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would prefer this solution.
@eps1lon, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me. You two seem to know more about built-in types for this problem so I can't help much.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found this as an example from the documentation:
fireEvent.keyDown(domNode, { key: 'Enter', code: 'Enter' })
The case of the keys of eventMap
seems to be part of the public API. Changing this would be a breaking change. I propose to have keydown
in eventMap
(so the key will be type checked against the lib.dom.ts interface), and add keyDown
into eventAliasMap
I'll do a commit this day to show the changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've started something, and now i've to maintain the camelcase event list into the eventAliasMap
:
export const eventAliasMap: Record<string, EventType> = {
doubleClick: 'dblclick',
// Aliases to keep the API with camelcase events
// Composition Events
compositionEnd: 'compositionend',
compositionStart: 'compositionstart',
compositionUpdate: 'compositionupdate',
// Keyboard Events
keyDown: 'keydown',
keyPress: 'keypress',
keyUp: 'keyup',
// Focus Events
focusIn: 'focusin',
focusOut: 'focusout',
// Mouse Events
contextMenu: 'contextmenu',
dblClick: 'dblclick',
dragEnd: 'dragend',
dragEnter: 'dragenter',
dragExit: 'dragexit',
dragLeave: 'dragleave',
dragOver: 'dragover',
dragStart: 'dragstart',
mouseDown: 'mousedown',
mouseEnter: 'mouseenter',
mouseLeave: 'mouseleave',
mouseMove: 'mousemove',
mouseOut: 'mouseout',
mouseOver: 'mouseover',
// ...more and more...
}
We will to need an AliasEventType
to correctly declare the all the fireEvent aliases (fireEvent.dblClick
, fireEvent.mouseEnter
, and so on). It seems we just shift the problem a little further.
The library API exposes camelcase events, which is more usable/readable for the end user IMHO. Maybe we should wait for the end of the TS migration before trying to simplify the code. For now, TS should be a "helper", not a constraint to build a clean API.
If you really want to see all the modifications i can spend more time on it this weekend. Let me know your opinions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really want to see all the modifications i can spend more time on it this weekend
Nah, let's not waste your time. I agree with:
Maybe we should wait for the end of the TS migration before trying to simplify the code.
We've (you've) explored the available options. It looks like we have the best one for this moment.
So let's get this thing merged 👍
types/event-map.d.ts
Outdated
} & KeyboardEventInit | ||
declare type EventMapValue = | ||
| { | ||
EventType: 'AnimationEvent' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want EventType
here to be a string, or the type it represents from lib.dom
?
EventType: 'AnimationEvent' | AnimationEvent
Not sure which I favor. I'm just curious about the reason why (pro/cons).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The string is used at runtime:
dom-testing-library/src/events.js
Lines 51 to 58 in 281eb8b
const EventConstructor = window[EventType] || window.Event | |
let event | |
/* istanbul ignore else */ | |
if (typeof EventConstructor === 'function') { | |
event = new EventConstructor(eventName, eventInit) | |
} else { | |
// IE11 polyfill from https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill | |
event = window.document.createEvent(EventType) |
So we need a TS macro to transform a TS interface name into string. I don't know how to do that.
I think we'll want to put a hold on this until #616 is merged (which should be early next week). |
Types can often help with big refactors/additions like this, there's often some extra churn involved but I usually feel it's worth it. Do you have any concerns about if merging this first would cause issues, or is it just that you'd prefer to prioritize the merge first? |
It's just because I think we may not be quite ready to merge this. And I really want to prioritize merging in user event. |
Thanks I'm fine with that, I just wasn't sure about the status |
FYI: The |
Here are the rest of the migration (only "src", not "tests") before my vacation. I think we can remove dtslint now the source uses TypeScript (and add the .d.ts files into .gitignore). |
Thanks @ghostd. Obviously there are a handful of merge conflicts which will need to be resolved before this is merged, but I'd like it if @testing-library/core-maintainers could review this, provide feedback, and we need to decide whether this is something we want to pursue. |
I'm a great fun of Typescript but sometimes it requires more time to understand and use, especially when types used are very restrictive. It could help to avoid some errors but at the same time we have a lot of tests to avoid them. |
That's precisely why I've not added TypeScript to any of my open source projects. |
@@ -0,0 +1,55 @@ | |||
import * as defaultQueries from './queries' | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This area seems the most complex in the migraiton... I know we've gone through a few iterations on the typings for this. Hopefully it doesn't need to change often.
function fuzzyMatches(textToMatch, node, matcher, normalizer) { | ||
export type MatcherFunction = (content: string, element: HTMLElement) => boolean | ||
|
||
export type Matcher = string | RegExp | MatcherFunction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The types for these options are quite helpful
{exact = true, collapseWhitespace, trim, normalizer} = {}, | ||
container: HTMLElement, | ||
value: Matcher, | ||
{exact = true, collapseWhitespace, trim, normalizer}: MatcherOptions = {}, |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
@@ -35,13 +38,19 @@ function queryAllLabelsByText( | |||
}) | |||
|
|||
return matcher(textToMatch, label, text, matchNormalizer) | |||
}) | |||
}) as HTMLElement[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't inferrable? I wonder what the type is without the as
, when you filter a NodeList.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is widening the type from HTMLLabelElement[]
. Though I don't know why this is intended.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually a nice constraint would be to add a queryAll function type that has HTMLElement[]
as the return type. Then if we mess up the implementation to return something else, it would break. Something like this:
function queryAllLabelsByText(/* */): QueryAllResult {
}
I'm not sure about the widening part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning an HTMLLabelElement
if HTMLElement
is expected is perfectly fine though since HTMLLabelElement extends HTMLElement
. The other way around would be problematic.
https://github.com/kentcdodds/kcd-scripts/releases/tag/v7.2.0 |
Maeby we can start migrating @testing-library/react that is smaller and than we can try with this ? |
I think it would still be best to start here, as most packages (including React Testing Library and User Event) depend on it. If we migrate dependent packages first, and then change the types emitted in DOM Testing Library, then we may have to update the dependent packages again. Finishing migrating DOM Testing Library (including using this script and resolving merge conflicts) first may be more work up front, but I think it will be less work in the long run and it will make it easier to keep types correct when migrating dependent packages. Let me know if any help is needed. |
I think it would be best to avoid doing the migration all at once and instead do it over time and only start generating the type defs when we're finished. |
Does that mean that we're updating or closing this pull request? I may be able to help but I'm not sure where to continue. |
Probably much easier to close at this point |
What:
Add a TypeScript config to generate the declaration files from the TS source files.
Why:
The aim is to make the TS transition easier (and avoid big PR as mentioned here)
How:
I added a "manual" script
generate-types
which generates the declaration files from the TS source files. This approach allows us to migrate each JS file one by one. I don't know what could be the best way to insert this command into the build/validation process.This PR includes a first migration (see
get-node-text.ts
)Checklist:
docs site
DefinitelyTyped "N/A"
Any thoughts are welcome.
#494