diff --git a/guides/release/accessibility/page-template-considerations.md b/guides/release/accessibility/page-template-considerations.md index 8ad0724f31..8d616240b2 100644 --- a/guides/release/accessibility/page-template-considerations.md +++ b/guides/release/accessibility/page-template-considerations.md @@ -17,29 +17,32 @@ Consider this format: Note that the unique page title is first. This is because it is the most important piece of information from a contextual perspective. Since a user with a screen reader can interrupt the screen reader as they wish, it introduces less fatigue when the unique page title is first, but provides the additional guidance if it is desired. -A simple way to add page titles is to use the `page-title` helper which comes from the [ember-page-title](https://github.com/ember-cli/ember-page-title) addon that is installed by default in new apps. We can use this helper to set the page title at any point in any template. +A simple way to add page titles is to use the `pageTitle` helper which comes from the [ember-page-title](https://github.com/ember-cli/ember-page-title) addon that is installed by default in new apps. We can use this helper to set the page title at any point in any template. For example, if we have a “posts” route, we can set the page title for it like so: +```gjs {data-filename=app/routes/posts.gjs} +import { pageTitle } from 'ember-page-title'; -```handlebars {data-filename=app/routes/posts.hbs} -{{page-title "Posts - Site Title"}} - -{{outlet}} + ``` Extending the example, if we have a “post” route that lives within the “posts” route, we could set its page title like so: -```handlebars {data-filename=app/routes/posts/post.hbs} -{{page-title (concat @model.title " - Site Title")}} +```gjs {data-filename=app/routes/posts/post.gjs} +import { pageTitle } from 'ember-page-title'; -

{{@model.title}}

-``` + +``` -- [ember-cli-head](https://github.com/ronco/ember-cli-head) -- [ember-cli-document-title](https://github.com/kimroen/ember-cli-document-title) +Each call to the `{{pageTitle}}` helper will prepend the title string to the existing title all the way up to the root title in `application.gts`. So, if your application is titled "My App", then the full title for the above example would be "My Title | Posts | My App". To evaluate more addons to add/manage content in the `` of a page, view this category on [Ember Observer](https://emberobserver.com/categories/header-content). @@ -48,14 +51,14 @@ You can test that page titles are generated correctly by asserting on the value ```javascript {data-filename=tests/acceptance/posts-test.js} import { module, test } from 'qunit'; import { visit, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'my-app-name/tests/helpers'; +import { setupApplicationTest } from 'my-app/tests/helpers'; -module('Acceptance | posts', function(hooks) { +module('Acceptance | posts', function (hooks) { setupApplicationTest(hooks); - test('visiting /posts', async function(assert) { + test('visiting /posts', async function (assert) { await visit('/posts'); - assert.equal(document.title, 'Posts - Site Title'); + assert.equal(document.title, 'Posts | My App'); }); }); ``` diff --git a/guides/release/models/index.md b/guides/release/models/index.md index fc61fd0aac..ccb506441c 100644 --- a/guides/release/models/index.md +++ b/guides/release/models/index.md @@ -90,9 +90,9 @@ writing the admin section of a blogging app, which has a feature that lists the drafts for the currently logged in user. You might be tempted to make the component responsible for fetching that -data and storing it: +data and storing it and showing the list of drafts, like this: -```javascript {data-filename=app/components/list-of-drafts.js} +```gjs {data-filename=app/components/list-of-drafts.gjs} import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; import fetch from "fetch"; @@ -107,30 +107,27 @@ export default class ListOfDraftsComponent extends Component { this.drafts = data; }); } + } ``` -You could then show the list of drafts in your component's template like -this: - -```handlebars {data-filename=app/components/list-of-drafts.hbs} - -``` - -This works great for the `list-of-drafts` component. However, your app +This works great for the `ListOfDrafts` component. However, your app is likely made up of many different components. On another page you may want a component to display the number of drafts. You may be -tempted to copy and paste your existing `willRender` code into the new +tempted to copy and paste your existing `constructor` code into the new component. -```javascript {data-filename=app/components/drafts-button.js} +```gjs {data-filename=app/components/drafts-button.gjs} import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; import fetch from "fetch"; +import { LinkTo } from '@ember/routing'; export default class DraftsButtonComponent extends Component { @tracked drafts; @@ -142,13 +139,13 @@ export default class DraftsButtonComponent extends Component { this.drafts = data; }); } -} -``` -```handlebars {data-filename=app/components/drafts-button.hbs} - - Drafts ({{this.drafts.length}}) - + +} ``` Unfortunately, the app will now make two separate requests for the diff --git a/guides/release/services/index.md b/guides/release/services/index.md index 59d2627b89..d653adbd20 100644 --- a/guides/release/services/index.md +++ b/guides/release/services/index.md @@ -62,13 +62,17 @@ You can either invoke it with no arguments, or you can pass it the registered na When no arguments are passed, the service is loaded based on the name of the decorated property. You can load the shopping cart service with no arguments like below. -```javascript {data-filename=app/components/cart-contents.js} +```gjs {data-filename=app/components/cart-contents.gjs} import Component from '@glimmer/component'; import { service } from '@ember/service'; export default class CartContentsComponent extends Component { // Will load the service defined in: app/services/shopping-cart.js @service shoppingCart; + + } ``` @@ -76,13 +80,17 @@ This injects the shopping cart service into the component and makes it available Another way to inject a service is to provide the name of the service as an argument to the decorator. -```javascript {data-filename=app/components/cart-contents.js} +```gjs {data-filename=app/components/cart-contents.gjs} import Component from '@glimmer/component'; import { service } from '@ember/service'; export default class CartContentsComponent extends Component { // Will load the service defined in: app/services/shopping-cart.js @service('shopping-cart') cart; + + } ``` @@ -92,7 +100,7 @@ Sometimes a service may or may not exist, like when an initializer conditionally Since normal injection will throw an error if the service doesn't exist, you must look up the service using Ember's [`getOwner`](https://api.emberjs.com/ember/release/classes/@ember%2Fapplication/methods/getOwner?anchor=getOwner) instead. -```javascript {data-filename=app/components/cart-contents.js} +```gjs {data-filename=app/components/cart-contents.gjs} import Component from '@glimmer/component'; import { getOwner } from '@ember/application'; @@ -101,6 +109,10 @@ export default class CartContentsComponent extends Component { get cart() { return getOwner(this).lookup('service:shopping-cart'); } + + } ``` @@ -108,35 +120,35 @@ Injected properties are lazy loaded; meaning the service will not be instantiate Once loaded, a service will persist until the application exits. +Once injected into a component, a service can also be used in the template. + Below we add a remove action to the `cart-contents` component. -```javascript {data-filename=app/components/cart-contents.js} +```gjs {data-filename=app/components/cart-contents.gjs} import Component from '@glimmer/component'; import { service } from '@ember/service'; -import { action } from '@ember/object'; +import { on } from '@ember/modifier'; +import { fn } from '@ember/helper'; export default class CartContentsComponent extends Component { @service('shopping-cart') cart; - @action - remove(item) { + remove = (item) => { this.cart.remove(item); - } + }; + + } ``` -Once injected into a component, a service can also be used in the template. -Note `cart` being used below to get data from the cart. - -```handlebars {data-filename=app/components/cart-contents.hbs} - -``` - diff --git a/guides/release/typescript/additional-resources/gotchas.md b/guides/release/typescript/additional-resources/gotchas.md index d54caa1b91..74be3c9241 100644 --- a/guides/release/typescript/additional-resources/gotchas.md +++ b/guides/release/typescript/additional-resources/gotchas.md @@ -54,34 +54,11 @@ For examples, see: - EmberData [`@belongsTo`][model-belongsto] - EmberData [`@hasMany`][model-hasmany] -## Templates - -Templates are currently totally non-type-checked. This means that you lose any safety when moving into a template context, even if using a Glimmer `Component` in Ember Octane. (Looking for type-checking in templates? Try [Glint][]!) - -For example, TypeScript won't detect a mismatch between this action and the corresponding call in the template: - -```typescript {data-filename="app/components/my-game.ts"} -import Component from '@ember/component'; -import { action } from '@ember/object'; - -export default class MyGame extends Component { - @action turnWheel(degrees: number) { - // ... - } -} -``` - -```handlebars {data-filename="app/components/my-game.hbs"} - -``` - ## Hook Types and Autocomplete Let's imagine a component which just logs the names of its arguments when it is first constructed. First, we must define the [Signature][] and pass it into our component, then we can use the `Args` member in our Signature to set the type of `args` in the constructor: -```typescript {data-filename="app/components/args-display.ts"} +```gts {data-filename="app/components/args-display.gts"} import type Owner from '@ember/owner'; import Component from '@glimmer/component'; @@ -100,6 +77,9 @@ export default class ArgsDisplay extends Component { super(owner, args); Object.keys(args).forEach(log); } + + } ``` @@ -136,4 +116,3 @@ export default class MyRoute extends Route { [declare]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier -[glint]: https://typed-ember.gitbook.io/glint/ diff --git a/guides/release/typescript/application-development/testing.md b/guides/release/typescript/application-development/testing.md index d47c9e3087..f6ce154a16 100644 --- a/guides/release/typescript/application-development/testing.md +++ b/guides/release/typescript/application-development/testing.md @@ -17,22 +17,10 @@ export default interface User { Then our component might be defined like this: -```handlebars {data-filename="app/components/profile.hbs"} -
- {{this.description}} - {{@user.displayName}} -
-``` - -```typescript {data-filename="app/components/profile.ts"} +```gts {data-filename="app/components/profile.gts"} import Component from '@glimmer/component'; import type User from 'app/types/user'; -import { randomAvatarURL } from 'app/utils/avatar'; +import { randomAvatarURL } from 'my-app/utils/avatar'; interface ProfileSignature { Args: { @@ -50,18 +38,29 @@ export default class Profile extends Component { ? `${this.args.user.displayName}'s custom profile picture` : 'a randomly generated placeholder avatar'; } + + } ``` To test the `Profile` component, we need to set up a `User` on `this` to pass into the component as an argument. With TypeScript on our side, we can even make sure our user actually has the correct type! -```typescript {data-filename="tests/integration/components/profile.ts"} +```gts {data-filename="tests/integration/components/profile.gts"} import { module, test } from 'qunit'; import { render } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; -import { setupRenderingTest } from 'app/tests/helpers'; -import type User from 'app/types/user'; +import { setupRenderingTest } from 'my-app/tests/helpers'; +import type User from 'my-app/types/user'; module('Integration | Component | Profile', function (hooks) { setupRenderingTest(hooks); @@ -73,7 +72,7 @@ module('Integration | Component | Profile', function (hooks) { }; this.user = user; - await render(hbs`); assert.dom('[data-test-name]').hasText(this.user.displayName); assert @@ -88,7 +87,7 @@ module('Integration | Component | Profile', function (hooks) { }; this.user = user; - await render(hbs`); assert.dom('[data-test-name]').hasText(this.user.displayName); assert @@ -105,7 +104,7 @@ To inform TypeScript about this, we need to tell it that the type of `this` in e ```typescript {data-filename="tests/integration/components/profile.ts"} import type { TestContext } from '@ember/test-helpers'; -import type User from 'app/types/user'; +import type User from 'my-app/types/user'; interface Context extends TestContext { user: User; @@ -120,14 +119,13 @@ test('...', function (this: Context, assert) {}); Putting it all together, this is what our updated test definition would look like: -```typescript {data-filename="tests/integration/components/profile.ts"} +```gts {data-filename="tests/integration/components/profile.gts"} import { module, test } from 'qunit'; import { render } from '@ember/test-helpers'; import type { TestContext } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; -import { setupRenderingTest } from 'app/tests/helpers'; -import type User from 'app/types/user'; +import { setupRenderingTest } from 'my-app/tests/helpers'; +import type User from 'my-app/types/user'; interface Context extends TestContext { user: User; @@ -142,7 +140,7 @@ module('Integration | Component | Profile', function (hooks) { avatarUrl: 'https://example.com/star-wars/rey', }; - await render(hbs`); assert.dom('[data-test-name]').hasText(this.user.displayName); assert @@ -156,7 +154,7 @@ module('Integration | Component | Profile', function (hooks) { displayName: 'Rey', }; - await render(hbs`); assert.dom('[data-test-name]').hasText(this.user.displayName); assert @@ -198,7 +196,7 @@ The test for our function might look something like this: ```javascript {data-filename="tests/unit/utils/math-test.js"} import { module, test } from 'qunit'; -import { add } from 'app/utils/math'; +import { add } from 'my-app/utils/math'; module('the `add` function', function (hooks) { test('adds numbers correctly', function (assert) { @@ -234,7 +232,7 @@ We can also drop the assertion from our function definition, because the _compil ```typescript {data-filename="tests/unit/utils/math-test.ts"} import { module, test } from 'qunit'; -import { add } from 'app/utils/math'; +import { add } from 'my-app/utils/math'; module('the `add` function', function (hooks) { test('adds numbers correctly', function (assert) { @@ -268,7 +266,7 @@ Now, in our test file, we're similarly back to testing all those extra scenarios ```typescript {data-filename="tests/unit/utils/math-test.ts"} import { module, test } from 'qunit'; -import { add } from 'app/utils/math'; +import { add } from 'my-app/utils/math'; module('the `add` function', function (hooks) { test('adds numbers correctly', function (assert) { diff --git a/guides/release/typescript/core-concepts/invokables.md b/guides/release/typescript/core-concepts/invokables.md index e14f50083a..8c0de1868e 100644 --- a/guides/release/typescript/core-concepts/invokables.md +++ b/guides/release/typescript/core-concepts/invokables.md @@ -81,31 +81,30 @@ For example, consider the `AudioPlayer` described in the There, we defined component which accepted a `srcUrl` argument and used a `play-when` modifier to manage the behavior of the element: -```typescript {data-filename="app/components/audio-player.ts"} +```gts {data-filename="app/components/audio-player.gts"} import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; -import { action } from '@ember/object'; +import playWhen from 'my-app/modifiers/play-when'; +import { on } from '@ember/modifier'; export default class AudioPlayer extends Component { @tracked isPlaying = false; - @action - play() { + play = () => { this.isPlaying = true; - } + }; - @action - pause() { + pause = () => { this.isPlaying = false; - } -} -``` + }; -```handlebars {data-filename="app/components/audio-player.hbs"} -