-
Notifications
You must be signed in to change notification settings - Fork 1
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
generate addon using Ember CLI v4.1.1 + add RouteInfoMock #1
base: main
Are you sure you want to change the base?
Conversation
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.
Awesome first pass! Thanks so much! (Sorry it took so long!)
interface RouteInfo { | ||
readonly name: string; | ||
readonly parent: RouteInfo | null; | ||
readonly child: RouteInfo | null; | ||
readonly localName: string; | ||
readonly params: { [name: string]: unknown } | undefined; | ||
readonly paramNames: string[]; | ||
readonly queryParams: { [name: string]: string }; | ||
readonly metadata: unknown; | ||
find( | ||
predicate: (this: any, routeInfo: RouteInfo, i: number) => boolean, | ||
thisArg?: any | ||
): RouteInfo | undefined; | ||
} |
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.
Here we should import the type from its current location in @ember/routing
:
interface RouteInfo { | |
readonly name: string; | |
readonly parent: RouteInfo | null; | |
readonly child: RouteInfo | null; | |
readonly localName: string; | |
readonly params: { [name: string]: unknown } | undefined; | |
readonly paramNames: string[]; | |
readonly queryParams: { [name: string]: string }; | |
readonly metadata: unknown; | |
find( | |
predicate: (this: any, routeInfo: RouteInfo, i: number) => boolean, | |
thisArg?: any | |
): RouteInfo | undefined; | |
} | |
import type RouteInfo from '@ember/routing/-private/route-info'; |
That way we don't have to worry about keeping this in sync, other than to update the import location when we have a public API import for that type (soon!).
predicate: (this: any, routeInfo: RouteInfo, i: number) => boolean, | ||
thisArg?: any |
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.
A couple notes:
- We want to not have
any
here, and thethis
must always be aRouteInfo
. - We should need to actually match the original type for the
predicate
, so:
predicate: (this: any, routeInfo: RouteInfo, i: number) => boolean, | |
thisArg?: any | |
predicate: (this: RouteInfo, routeInfo: RouteInfo, idx: number, array?: RouteInfo[]) => boolean, | |
thisArg?: RouteInfo |
(I know the actual implementation has any
s there, but 😭.)
predicate: (this: any, routeInfo: RouteInfo, i: number) => boolean, | ||
thisArg?: any | ||
): RouteInfo | undefined { | ||
let current = thisArg || 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.
It's usually preferable to use the ??
nullish coalescing operator for these:
let current = thisArg || this; | |
let current = thisArg ?? this; |
parent: RouteInfo | null = null; | ||
child: RouteInfo | null = null; | ||
localName: string; | ||
params: { [name: string]: unknown } = {}; |
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.
Making the above change to use the type from Ember highlights that this isn't quite right and should instead be:
params: { [name: string]: unknown } = {}; | |
params: Record<string, string> = {}; |
return this; | ||
} | ||
|
||
withParams(params: { [name: string]: unknown }): RouteInfoMock { |
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 also needs updating for params
' type:
withParams(params: { [name: string]: unknown }): RouteInfoMock { | |
withParams(params: Record<string, string>): RouteInfoMock { |
|
||
return undefined; | ||
} | ||
} |
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.
We should add a finalize
method which just returns this
but cast as RouteInfo
so it can be treated as an immutable RouteInfo
from that point forward.
} | |
finalize(): RouteInfo { | |
return this; | |
} | |
} |
That has the effect of "removing" the builder methods from the public type signature of the thing after that call.
let i = 0; | ||
|
||
while (current) { | ||
if (predicate(current, i++)) { | ||
return current; | ||
} | ||
|
||
current = current.parent; | ||
} | ||
|
||
return undefined; |
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.
Thinking about this led me to realize that we actually want to separate out the mock content from the builder. The idea is: by separating out the mock (and, critically, not exposing it) you can do a few things:
-
You can make it so that the public interface is just "how to build a
RouteInfo
" and then "finalize it", per my comment below, which prevents people from mucking directly with it. If we add in some module-local access (using symbols), we can actually make it so the resultingRouteInfo
mocks have the same runtime (not just type-level) read-only behavior as the real version does. -
It lets us actually build up an appropriate list for this, and do it only once per finalized
RouteInfo
instance. Since that is only a thing users have access to once they finalize the builder, it is safe to cache it per-instance.
I have a version of that (along with the other changes suggested for the class) here—the comments on that walk through the changes! Note that in this design, as you can see at the bottom, you can build up mocks iteratively over time (as you basically have to be able to do) because the builder remains mutable—but the resulting mocks are not directly mutable.
@@ -0,0 +1,56 @@ | |||
{ | |||
"compilerOptions": { |
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.
Let's either—
- update to the latest version of ember-cli-typescript and re-run
ember generate ember-cli-typescript
- or manually copy over the blueprint's
compilerOptions
for everything butpaths
—that will give us a much better version of this tsconfig.json
!
This change generates foundational addon files using
Ember CLI v4.1.1
.